<?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: Hagicode</title>
    <description>The latest articles on Forem by Hagicode (@newbe36524).</description>
    <link>https://forem.com/newbe36524</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%2F588826%2Ff5bd5c70-a7e9-435d-b87c-43c73d4cff66.png</url>
      <title>Forem: Hagicode</title>
      <link>https://forem.com/newbe36524</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/newbe36524"/>
    <language>en</language>
    <item>
      <title>AI Output Token Optimization: Practice of Classical Chinese Ultra-Minimal Mode</title>
      <dc:creator>Hagicode</dc:creator>
      <pubDate>Sat, 04 Apr 2026 02:27:21 +0000</pubDate>
      <link>https://forem.com/newbe36524/ai-output-token-optimization-practice-of-classical-chinese-ultra-minimal-mode-3e63</link>
      <guid>https://forem.com/newbe36524/ai-output-token-optimization-practice-of-classical-chinese-ultra-minimal-mode-3e63</guid>
      <description>&lt;h1&gt;
  
  
  AI Output Token Optimization: Practice of Classical Chinese Ultra-Minimal Mode
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;In AI application development, token consumption directly impacts costs. The HagiCode project implements a "Classical Chinese Ultra-Minimal Output Mode" through the SOUL system, reducing output tokens by approximately 30-50% without compromising information density. This article shares the implementation details and usage experience of this solution.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;In AI application development, token consumption is an unavoidable cost issue. Especially in scenarios requiring AI to output large amounts of content, figuring out how to reduce output tokens without sacrificing information density can become quite a headache if dwelled upon too much.&lt;/p&gt;

&lt;p&gt;Traditional optimization approaches focus on the input side: streamlining system prompts, compressing context, and using more efficient encoding methods. However, these methods eventually hit a ceiling—further compression may affect AI's understanding capability and output quality. This is tantamount to cutting content, which holds little significance.&lt;/p&gt;

&lt;p&gt;What about the output side? Can we make AI express the same meaning more concisely?&lt;/p&gt;

&lt;p&gt;This question seems simple but actually contains considerable nuance. Simply telling AI to "be concise" might result in just a few words; adding "maintain complete information" may cause it to revert to its original verbose style. Too strong constraints affect usability, too weak constraints have no effect—and no one can say exactly where that balance point lies.&lt;/p&gt;

&lt;p&gt;To address these pain points, we made a bold decision: start from language style and design a configurable, composable expression constraint system. The changes brought by this decision might be greater than you imagine—I'll elaborate shortly, perhaps you'll be pleasantly surprised.&lt;/p&gt;

&lt;h2&gt;
  
  
  About HagiCode
&lt;/h2&gt;

&lt;p&gt;The solution shared in this article comes from our practical experience in the &lt;a href="https://hagicode.com" rel="noopener noreferrer"&gt;HagiCode&lt;/a&gt; project.&lt;/p&gt;

&lt;p&gt;HagiCode is an open-source AI coding assistant project supporting multiple AI models and custom configurations. During development, we discovered the issue of excessive AI output tokens and designed a solution. If you find this solution valuable, it demonstrates our engineering capability is quite solid—then HagiCode itself is worth attention, after all, code doesn't lie.&lt;/p&gt;

&lt;h2&gt;
  
  
  SOUL System Overview
&lt;/h2&gt;

&lt;p&gt;The SOUL system's full name is Soul Oriented Universal Language, a configuration system in the HagiCode project for defining AI Hero language styles. Its core idea is: by constraining AI's expression method, use more concise language forms to output content while maintaining information integrity.&lt;/p&gt;

&lt;p&gt;This thing is like putting a language mask on AI... well, actually it's not that mysterious.&lt;/p&gt;

&lt;h3&gt;
  
  
  Technical Architecture
&lt;/h3&gt;

&lt;p&gt;The SOUL system adopts a frontend-backend separated architecture:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Frontend (Soul Builder)&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Built with React + TypeScript + Vite&lt;/li&gt;
&lt;li&gt;Located in &lt;code&gt;repos/soul/&lt;/code&gt; directory&lt;/li&gt;
&lt;li&gt;Provides visual Soul building interface&lt;/li&gt;
&lt;li&gt;Supports bilingual (zh-CN / en-US)&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Based on .NET (C#) + Orleans distributed runtime&lt;/li&gt;
&lt;li&gt;Hero entity includes &lt;code&gt;Soul&lt;/code&gt; field (maximum 8000 characters)&lt;/li&gt;
&lt;li&gt;Injects Soul into system prompts through &lt;code&gt;SessionSystemMessageCompiler&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Agent Templates Generation&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generated from reference materials&lt;/li&gt;
&lt;li&gt;Output to &lt;code&gt;/agent-templates/soul/templates/&lt;/code&gt; directory&lt;/li&gt;
&lt;li&gt;Contains 50 main Catalog groups and 10 orthogonal dimension groups&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Soul Injection Mechanism
&lt;/h3&gt;

&lt;p&gt;When Session executes for the first time, the system reads the Hero's Soul configuration and injects it into the system prompt:&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 UI as 用户界面
    participant Session as SessionGrain
    participant Hero as Hero 仓库
    participant AI as AI 执行器

    UI-&amp;gt;&amp;gt;Session: 发送消息（绑定 Hero）
    Session-&amp;gt;&amp;gt;Hero: 读取 Hero.Soul
    Session-&amp;gt;&amp;gt;Session: 缓存 Soul 快照
    Session-&amp;gt;&amp;gt;AI: 构建 AIRequest（注入 Soul）
    AI--&amp;gt;&amp;gt;Session: 执行结果
    Session--&amp;gt;&amp;gt;UI: 流式响应
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The injected system prompt format is:&lt;br&gt;
&lt;/p&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;hero_soul&amp;gt;&lt;/span&gt;
[User's custom Soul content]
&lt;span class="nt"&gt;&amp;lt;/hero_soul&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This injection mechanism is implemented in &lt;code&gt;SessionSystemMessageCompiler.cs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;internal&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;BuildSystemMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;existingSystemMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;languagePreference&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;IReadOnlyList&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;HeroTraitDto&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;?&lt;/span&gt; &lt;span class="n"&gt;traits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;soul&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;segments&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// ... 语言偏好和 Traits 处理 ...&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;normalizedSoul&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;NormalizeSoul&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;soul&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="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsNullOrWhiteSpace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;normalizedSoul&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"&amp;lt;hero_soul&amp;gt;\n&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;normalizedSoul&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;\n&amp;lt;/hero_soul&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;// ... 其他系统消息 ...&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&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="s"&gt;"\n\n"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;segments&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;Code reviewed, principles understood—essentially, that's all there is to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Classical Chinese Ultra-Minimal Mode
&lt;/h2&gt;

&lt;p&gt;Classical Chinese Ultra-Minimal Mode is the most representative token-saving solution in the SOUL system. Its core principle is leveraging Classical Chinese's high semantic density to compress output length while maintaining information integrity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Classical Chinese
&lt;/h3&gt;

&lt;p&gt;Classical Chinese has several natural advantages:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Semantic Compression&lt;/strong&gt;: The same meaning can be expressed with fewer characters&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Removing Redundancy&lt;/strong&gt;: Classical Chinese inherently omits many conjunctions and particles found in modern Chinese&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Concise Structure&lt;/strong&gt;: High information density per sentence, suitable as an AI output carrier&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A practical example to illustrate:&lt;/p&gt;

&lt;p&gt;Modern Chinese output (approximately 80 characters):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;根据你的代码分析，我发现了几个问题。首先，在第 23 行，变量名太长了，建议缩短一些。其次，在第 45 行，你没有处理空值的情况，应该加上判断逻辑。最后，整体的代码结构还可以，但是可以进一步优化。
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Classical Chinese ultra-minimal output (approximately 35 characters, 56% savings):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;代码审阅毕：第 23 行变量名冗长，宜缩写；第 45 行缺空值处理，应加判断。整体结构尚可，微调即可。
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This difference is quite interesting when you think about it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Soul Configuration Template
&lt;/h3&gt;

&lt;p&gt;The complete Soul configuration for Classical Chinese Ultra-Minimal Mode is as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"soul-orth-11-classical-chinese-ultra-minimal-mode"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;"summary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"以尽量可懂的文言文压缩语义密度，尽可能少字达意，只保留结论、判断与必要动作，从而大幅降低输出 token"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"soul"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"你的人设内核来自「文言文极简输出模式」：以尽量可懂的文言文压缩语义密度，尽可能少字达意，只保留结论、判断与必要动作，从而大幅降低输出 token。&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;保持以下标志性语言特征：1. 优先使用简明文言句式，如「可」「宜」「勿」「已」「然」「故」等，避免生僻艰涩字词；&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;2. 单句尽量压缩至 4-12 字，删除铺垫、寒暄、重复解释与无效修饰；&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;3. 非必要不展开论证，用户未追问则只给结论、步骤或判断；&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;4. 不改变主 Catalog 的核心人设，只将表达收束为克制、古雅、极简的短句。"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This template's design has several key points:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Clear Constraints&lt;/strong&gt;: 4-12 characters per sentence, remove redundancy, conclusions first&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Avoid Obscurity&lt;/strong&gt;: Use simple Classical Chinese sentence patterns, avoid rare words&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maintain Persona&lt;/strong&gt;: Only change expression method, not core persona&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Configuration tuning is just a matter of adjusting a few parameters, really.&lt;/p&gt;

&lt;h3&gt;
  
  
  Other Minimal Modes
&lt;/h3&gt;

&lt;p&gt;Beyond Classical Chinese mode, HagiCode's SOUL system provides various other token-saving modes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Telegraphic Ultra-Minimal Output Mode&lt;/strong&gt; (&lt;code&gt;soul-orth-02&lt;/code&gt;):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Single sentences strictly controlled within 10 characters&lt;/li&gt;
&lt;li&gt;Prohibits decorative adjectives&lt;/li&gt;
&lt;li&gt;No modal particles, exclamation marks, or reduplicated words throughout&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Short Sentence Murmur Mode&lt;/strong&gt; (&lt;code&gt;soul-orth-01&lt;/code&gt;):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sentences controlled at 1-5 characters&lt;/li&gt;
&lt;li&gt;Simulates fragmented self-talk expression&lt;/li&gt;
&lt;li&gt;Weakened logic, prioritize emotional transmission&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Guided Q&amp;amp;A Mode&lt;/strong&gt; (&lt;code&gt;soul-orth-03&lt;/code&gt;):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Guide user thinking through questions&lt;/li&gt;
&lt;li&gt;Reduce direct output content&lt;/li&gt;
&lt;li&gt;Interactive token consumption reduction&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each mode has different design priorities, but the core goal is consistent: reduce output tokens while maintaining information quality. All roads lead to Rome—some paths are just easier to walk than others.&lt;/p&gt;

&lt;h2&gt;
  
  
  Combination Strategy
&lt;/h2&gt;

&lt;p&gt;A powerful feature of the SOUL system is support for cross-combination of main Catalogs and orthogonal dimensions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;50 Main Catalog Groups&lt;/strong&gt;: Define base personas (such as healing style, academic style, cool style, etc.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;10 Orthogonal Dimensions&lt;/strong&gt;: Define expression methods (such as Classical Chinese, telegraphic, Q&amp;amp;A style, etc.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Combination Effect&lt;/strong&gt;: Can generate 500+ unique language style combinations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, you can combine "Professional Development Engineer" with "Classical Chinese Ultra-Minimal Output Mode" to get an AI assistant that is both professional and concise. This flexibility allows the SOUL system to adapt to various usage scenarios. Combine however you want—there are more combinations than you can possibly explore...&lt;/p&gt;

&lt;h2&gt;
  
  
  Practice Guide
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Creating via Soul Builder
&lt;/h3&gt;

&lt;p&gt;Visit &lt;a href="https://soul.hagicode.com" rel="noopener noreferrer"&gt;soul.hagicode.com&lt;/a&gt; and follow these steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Select main Catalog (such as "Professional Development Engineer")&lt;/li&gt;
&lt;li&gt;Select orthogonal dimension (such as "Classical Chinese Ultra-Minimal Output Mode")&lt;/li&gt;
&lt;li&gt;Preview generated Soul content&lt;/li&gt;
&lt;li&gt;Copy generated Soul configuration&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Point-and-click operations, shouldn't need much explanation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using in Hero Configuration
&lt;/h3&gt;

&lt;p&gt;Apply Soul configuration to Hero through Web interface or API:&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;// Hero Soul update example&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;heroUpdate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;soul&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;soulCatalogId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;soul-orth-11-classical-chinese-ultra-minimal-mode&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;soulDisplayName&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;soulStyleType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;orthogonal-dimension&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;soulSummary&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="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;updateHero&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;heroId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;heroUpdate&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Custom Soul Templates
&lt;/h3&gt;

&lt;p&gt;Users can fine-tune based on preset templates or completely customize. Here's a custom example for code review scenarios:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;你是一位追求极致简洁的代码审查员。
所有输出必须遵循：
1. 仅指出具体问题和行号
2. 每条问题不超过 15 字
3. 使用「宜」「应」「勿」等简洁词汇
4. 不做多余解释

示例输出：
- 第 23 行：变量名过长，宜缩写
- 第 45 行：未处理空值，应加判断
- 第 67 行：逻辑冗余，可简化
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Modify however you want—templates are just a starting point anyway.&lt;/p&gt;

&lt;h3&gt;
  
  
  Notes
&lt;/h3&gt;

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

&lt;ul&gt;
&lt;li&gt;Classical Chinese mode adapts to all 50 main Catalog groups&lt;/li&gt;
&lt;li&gt;Can be combined with any base persona&lt;/li&gt;
&lt;li&gt;Does not alter main Catalog's core persona&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Caching Mechanism&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Soul is cached when Session first executes&lt;/li&gt;
&lt;li&gt;Cache is reused within the same SessionId&lt;/li&gt;
&lt;li&gt;Modifying Hero configuration does not affect already started Sessions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Limitation Constraints&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Soul field maximum length is 8000 characters&lt;/li&gt;
&lt;li&gt;Heroes without Soul field in historical data can still be used normally&lt;/li&gt;
&lt;li&gt;Soul is independent from style equipment slot, will not overwrite each other&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Effect Comparison
&lt;/h2&gt;

&lt;p&gt;Based on actual test data from the project, the effects after using Classical Chinese Ultra-Minimal Mode are as follows:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Original Output Token&lt;/th&gt;
&lt;th&gt;Classical Chinese Mode&lt;/th&gt;
&lt;th&gt;Savings Ratio&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Code Review&lt;/td&gt;
&lt;td&gt;850&lt;/td&gt;
&lt;td&gt;420&lt;/td&gt;
&lt;td&gt;51%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Technical Q&amp;amp;A&lt;/td&gt;
&lt;td&gt;620&lt;/td&gt;
&lt;td&gt;380&lt;/td&gt;
&lt;td&gt;39%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Solution Suggestions&lt;/td&gt;
&lt;td&gt;1100&lt;/td&gt;
&lt;td&gt;680&lt;/td&gt;
&lt;td&gt;38%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Average&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;30-50%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Data comes from actual usage statistics of the HagiCode project, specific effects vary by scenario. However, the saved tokens accumulate over time—your wallet will thank you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;HagiCode's SOUL system provides an innovative AI output optimization approach: reducing token consumption by constraining expression methods rather than compressing information itself. As the most representative solution, Classical Chinese Ultra-Minimal Mode has achieved 30-50% token savings in actual use.&lt;/p&gt;

&lt;p&gt;The core value of this solution lies in:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Maintaining Information Quality&lt;/strong&gt;: Not simply truncating output, but expressing more efficiently&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flexible and Composable&lt;/strong&gt;: Supports 500+ combinations of personas and expression methods&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Easy to Use&lt;/strong&gt;: Through Soul Builder visual interface, no coding required&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Production-Grade Stability&lt;/strong&gt;: Verified in project, supports large-scale usage&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you're also developing AI applications or interested in the HagiCode project, welcome to exchange ideas. The meaning of open source is progress together, and I look forward to seeing your innovative usage. After all, one person walks fast, a group walks far... a bit cliché, but that's how it is.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;HagiCode GitHub: &lt;a href="https://github.com/HagiCode-org/site" rel="noopener noreferrer"&gt;github.com/HagiCode-org/site&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;HagiCode Official Site: &lt;a href="https://hagicode.com" rel="noopener noreferrer"&gt;hagicode.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Soul Builder: &lt;a href="https://soul.hagicode.com" rel="noopener noreferrer"&gt;soul.hagicode.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Docker Deployment Guide: &lt;a href="https://docs.hagicode.com/installation/docker-compose" rel="noopener noreferrer"&gt;docs.hagicode.com/installation/docker-compose&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Desktop Client: &lt;a href="https://hagicode.com/desktop/" rel="noopener noreferrer"&gt;hagicode.com/desktop/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;30-Minute Practical Demo: &lt;a href="https://www.bilibili.com/video/BV1pirZBuEzq/" rel="noopener noreferrer"&gt;www.bilibili.com/video/BV1pirZBuEzq/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;If this article helps you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Give a Star on GitHub: &lt;a href="https://github.com/HagiCode-org/site" rel="noopener noreferrer"&gt;github.com/HagiCode-org/site&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Visit the official site to learn more: &lt;a href="https://hagicode.com" rel="noopener noreferrer"&gt;hagicode.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Beta testing has started, welcome to install and experience&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Original Article &amp;amp; License
&lt;/h2&gt;

&lt;p&gt;Thanks for reading. If this article helped, consider liking, bookmarking, or sharing it.&lt;br&gt;
This article was created with AI assistance and reviewed by the author before publication.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Author: &lt;a href="https://www.newbe.pro" rel="noopener noreferrer"&gt;newbe36524&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Original URL: &lt;a href="https://docs.hagicode.com/go?platform=devto&amp;amp;target=%2Fblog%2F2026-04-04-soul-token-optimization-classical-chinese%2F" rel="noopener noreferrer"&gt;https://docs.hagicode.com/go?platform=devto&amp;amp;target=%2Fblog%2F2026-04-04-soul-token-optimization-classical-chinese%2F&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;License: Unless otherwise stated, this article is licensed under CC BY-NC-SA. Please retain attribution when sharing.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>token</category>
      <category>hagicode</category>
      <category>soul</category>
    </item>
    <item>
      <title>From CLI Invocation to SDK Integration: Best Practices for GitHub Copilot in .NET Projects</title>
      <dc:creator>Hagicode</dc:creator>
      <pubDate>Fri, 03 Apr 2026 02:05:09 +0000</pubDate>
      <link>https://forem.com/newbe36524/from-cli-invocation-to-sdk-integration-best-practices-for-github-copilot-in-net-projects-4o21</link>
      <guid>https://forem.com/newbe36524/from-cli-invocation-to-sdk-integration-best-practices-for-github-copilot-in-net-projects-4o21</guid>
      <description>&lt;h1&gt;
  
  
  From CLI Invocation to SDK Integration: Best Practices for GitHub Copilot in .NET Projects
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;The upgrade path from command-line invocation to official SDK integration has been quite a journey. Today, I'll share the pitfalls we encountered and lessons learned in the HagiCode project.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;After the official release of the GitHub Copilot SDK in 2025, we began integrating it into our AI capability layer. Prior to this, the project primarily used GitHub Copilot capabilities by directly calling the Copilot CLI command-line tool. However, this approach presented several obvious issues:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Complex process management&lt;/strong&gt;: Need to manually manage CLI process lifecycle, startup timeouts, and process cleanup—after all, processes can crash without any warning&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Incomplete event handling&lt;/strong&gt;: Raw CLI invocation makes it difficult to capture fine-grained events during model inference and tool execution—like only seeing the result without witnessing the thinking process&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Difficult session management&lt;/strong&gt;: Lack of effective session reuse and recovery mechanisms, meaning starting from scratch every time, which gets quite exhausting&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compatibility issues&lt;/strong&gt;: CLI parameters update frequently, requiring continuous maintenance of parameter compatibility logic—fighting against windmills, essentially&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These issues gradually became apparent in daily development, especially when needing real-time tracking of model inference process (thinking) and tool execution status. The limitations of CLI invocation became particularly obvious. We eventually realized we needed a lower-level, more complete integration approach—after all, all roads lead to Rome, it's just that some roads are easier to travel than others.&lt;/p&gt;

&lt;h2&gt;
  
  
  About HagiCode
&lt;/h2&gt;

&lt;p&gt;The solution shared in this article comes from our practical experience in the &lt;a href="https://hagicode.com" rel="noopener noreferrer"&gt;HagiCode&lt;/a&gt; project. HagiCode is an open-source AI code assistant project. During development, we needed deep integration of various GitHub Copilot capabilities—from basic code completion to complex multi-turn conversations and tool calls. These actual requirements drove our upgrade from CLI invocation to official SDK integration.&lt;/p&gt;

&lt;p&gt;If you're interested in the practical solutions in this article, it means our engineering practices might be helpful to you—then the HagiCode project itself is worth checking out. Perhaps at the end of the article, you'll discover more information and links about the project, who knows...&lt;/p&gt;

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

&lt;p&gt;The project adopts a layered architecture to address CLI invocation issues:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────────────────────────┐
│  hagicode-core (Orleans Grains + AI Provider Layer)    │
│  - CopilotAIProvider: Convert AIRequest to CopilotOptions │
│  - GitHubCopilotGrain: Orleans distributed execution interface            │
└─────────────────────────────────────────────────────────┘
                          ↓
┌─────────────────────────────────────────────────────────┐
│  HagiCode.Libs (Shared Provider Layer)                 │
│  - CopilotProvider: CLI Provider interface implementation               │
│  - ICopilotSdkGateway: SDK invocation abstraction                     │
│  - GitHubCopilotSdkGateway: SDK session management &amp;amp; event distribution     │
└─────────────────────────────────────────────────────────┘
                          ↓
┌─────────────────────────────────────────────────────────┐
│  GitHub Copilot SDK (Official .NET SDK)                │
│  - CopilotClient: SDK client                            │
│  - CopilotSession: Session management                             │
│  - SessionEvent: Event stream                                 │
└─────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The technical advantages of this layered design are actually quite practical:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Separation of concerns&lt;/strong&gt;: Core business logic decoupled from SDK implementation details—after all, each layer has its responsibilities, not stepping on each other's toes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testability&lt;/strong&gt;: Through the &lt;code&gt;ICopilotSdkGateway&lt;/code&gt; interface, unit testing becomes straightforward, making testing less painful&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reusability&lt;/strong&gt;: HagiCode.Libs can be referenced by multiple projects, write once, use everywhere&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maintainability&lt;/strong&gt;: SDK upgrades only require modifying the Gateway layer, upper code remains untouched, quite nice&lt;/li&gt;
&lt;/ol&gt;

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

&lt;h3&gt;
  
  
  Authentication Flow
&lt;/h3&gt;

&lt;p&gt;Authentication is the first and most important step in SDK integration—after all, if you can't get through the door, nothing else matters. We designed a flexible authentication configuration that supports multiple authentication sources:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// CopilotProvider.cs - Authentication source configuration&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CopilotOptions&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;UseLoggedInUser&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&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;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;GitHubToken&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;CliUrl&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&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;// Convert to SDK request&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;CopilotSdkRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;GitHubToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AuthSource&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;CopilotAuthSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GitHubToken&lt;/span&gt;
        &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GitHubToken&lt;/span&gt;
        &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;UseLoggedInUser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AuthSource&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;CopilotAuthSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GitHubToken&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The benefits of this design are quite obvious:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Supports logged-in user mode (no token required), suitable for desktop scenarios—users log in with their own accounts&lt;/li&gt;
&lt;li&gt;Supports GitHub Token mode, applicable for server-side deployment—unified management is convenient&lt;/li&gt;
&lt;li&gt;Supports Copilot CLI URL override, convenient for enterprise proxy configuration—enterprise environments always have special rules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In actual use, this flexible authentication approach greatly simplifies configuration work for different deployment scenarios. Desktop clients can use the user's own Copilot login state, while servers can manage unified access through tokens. Different approaches for different needs, really.&lt;/p&gt;

&lt;h3&gt;
  
  
  Event Stream Processing
&lt;/h3&gt;

&lt;p&gt;One of the SDK's most powerful capabilities is complete event stream capture. We implemented an event dispatch system capable of real-time processing of various SDK events—after all, knowing the process versus just knowing the result feels quite different:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// GitHubCopilotSdkGateway.cs - Event dispatch core logic&lt;/span&gt;
&lt;span class="k"&gt;internal&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;SessionEventDispatchResult&lt;/span&gt; &lt;span class="nf"&gt;DispatchSessionEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;SessionEvent&lt;/span&gt; &lt;span class="n"&gt;evt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;sawDelta&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="n"&gt;evt&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="n"&gt;AssistantReasoningEvent&lt;/span&gt; &lt;span class="n"&gt;reasoningEvent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;// Capture model reasoning process&lt;/span&gt;
            &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;CopilotSdkStreamEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;CopilotSdkStreamEventType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReasoningDelta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;reasoningEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&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="n"&gt;ToolExecutionStartEvent&lt;/span&gt; &lt;span class="n"&gt;toolStartEvent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;// Capture tool call start&lt;/span&gt;
            &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;CopilotSdkStreamEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;CopilotSdkStreamEventType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ToolExecutionStart&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;ToolName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;toolStartEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ToolName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;ToolCallId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;toolStartEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ToolCallId&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="n"&gt;ToolExecutionCompleteEvent&lt;/span&gt; &lt;span class="n"&gt;toolCompleteEvent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;// Capture tool call completion and result&lt;/span&gt;
            &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;CopilotSdkStreamEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;CopilotSdkStreamEventType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ToolExecutionEnd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;ExtractToolExecutionContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;toolCompleteEvent&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;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;// Unhandled events preserved as RawEvent&lt;/span&gt;
            &lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;CopilotSdkStreamEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;CopilotSdkStreamEventType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RawEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;RawEventType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;evt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetType&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;Name&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="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 value this implementation brings, how should I put it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Complete capture of model reasoning process&lt;/strong&gt; (thinking): Users can see the AI's thinking process, not just the final result—like knowing how to think is better than just knowing the answer&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real-time tracking of tool execution status&lt;/strong&gt;: Know which tools are running, when they complete, and what results they return&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero event loss&lt;/strong&gt;: Through fallback to RawEvent mechanism, ensures all events are recorded, nothing gets left behind&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In HagiCode's actual use, these fine-grained events allow users to gain deeper understanding of the AI's work process, especially when debugging complex tasks—this is actually quite useful.&lt;/p&gt;

&lt;h3&gt;
  
  
  CLI Compatibility Handling
&lt;/h3&gt;

&lt;p&gt;After migrating from CLI invocation to SDK, we found that some original CLI parameters were no longer applicable in the SDK. To maintain backward compatibility, we implemented a parameter filtering system—after all, it's quite headache-inducing when old configurations don't work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// CopilotCliCompatibility.cs - Parameter filtering&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;RejectedFlags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&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="s"&gt;"--headless"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Unsupported startup parameter"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"--model"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Passed through SDK native fields"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"--prompt"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Passed through SDK native fields"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"--interactive"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Interaction managed by provider"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;CopilotCliArgumentBuildResult&lt;/span&gt; &lt;span class="nf"&gt;BuildCliArgs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CopilotOptions&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Filter unsupported parameters, retain compatible parameters&lt;/span&gt;
    &lt;span class="c1"&gt;// Generate diagnostic information&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Benefits of doing this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Automatically filters incompatible CLI parameters, avoiding runtime errors—program crashes are no joke&lt;/li&gt;
&lt;li&gt;Generates clear error diagnostic information, helping developers quickly locate problems&lt;/li&gt;
&lt;li&gt;Ensures SDK stability, unaffected by CLI parameter changes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;During the upgrade process, this compatibility handling mechanism helped us transition smoothly. Old configuration files can still be used, only requiring gradual adjustments based on diagnostic information—consider it a gradual process.&lt;/p&gt;

&lt;h3&gt;
  
  
  Runtime Pooling
&lt;/h3&gt;

&lt;p&gt;Creating Copilot SDK sessions is costly, and frequently creating and destroying sessions affects performance. We implemented a session pool management system—like water in a pool, better to keep it for next use rather than refilling each time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// CopilotProvider.cs - Session pool management&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;lease&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_poolCoordinator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AcquireCopilotRuntimeAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_gateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateRuntimeAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sdkRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;cancellationToken&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="n"&gt;lease&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsWarmLease&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Reuse existing session&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;CreateSessionReusedMessage&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="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;eventData&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;lease&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SendPromptAsync&lt;/span&gt;&lt;span class="p"&gt;(...))&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;MapEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;eventData&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;Benefits of session pooling:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Session reuse&lt;/strong&gt;: Requests with the same sessionId can reuse existing sessions, reducing startup overhead&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supports session recovery&lt;/strong&gt;: Previous session state can be recovered after network interruption—after all, who can guarantee networks are always stable&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automatic pool management&lt;/strong&gt;: Automatically cleans expired sessions, avoiding resource leaks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In HagiCode's actual use, session pooling significantly improved response speed, especially when handling continuous conversations—this improvement is quite noticeable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Orleans Integration
&lt;/h3&gt;

&lt;p&gt;HagiCode uses Orleans as its distributed framework, and we integrated the Copilot SDK into Orleans Grains—distributed systems, complex to talk about but quite smooth to use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// GitHubCopilotGrain.cs - Distributed execution&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;IAsyncEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;GitHubCopilotResponse&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;ExecuteCommandStreamAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;provider&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;aiProviderFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetProviderAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AIProviderType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GitHubCopilot&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SendMessageAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Map to unified response format&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;BuildChunkResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;startedAt&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;Advantages of Orleans integration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unified AI Provider abstraction&lt;/strong&gt;: Can easily switch between different AI providers—use this today, that tomorrow, quite flexible&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-tenant isolation&lt;/strong&gt;: Different users' Copilot sessions are isolated from each other, no interference&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Persistent session state&lt;/strong&gt;: Session state can recover across server restarts, no data loss from restarts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For scenarios needing to handle large volumes of concurrent requests, Orleans's distributed capabilities provide excellent scalability—after all, when a single machine can't handle it, distributed systems pick up the slack.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Guide
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Configuration Example
&lt;/h3&gt;

&lt;p&gt;Here's a complete configuration example—copy, paste, modify, and it works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"AI"&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;"Providers"&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;"Providers"&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;"GitHubCopilot"&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;"Enabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"ExecutablePath"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"copilot"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"Model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gpt-5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"WorkingDirectory"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/path/to/project"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"Timeout"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"StartupTimeout"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"UseLoggedInUser"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"NoAskUser"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"Permissions"&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;"AllowAllTools"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"AllowedTools"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Read"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bash"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Grep"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"DeniedTools"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Edit"&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="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="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="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Usage Considerations
&lt;/h3&gt;

&lt;p&gt;In actual use, we've summarized some points needing attention—some are lessons learned from pitfalls:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Startup timeout configuration&lt;/strong&gt;: First-time Copilot CLI startup takes longer, recommend setting &lt;code&gt;StartupTimeout&lt;/code&gt; to at least 30 seconds. For first-time login, even more time may be needed—after all, first-time login requires verification, can't be helped.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Permission management&lt;/strong&gt;: Avoid using &lt;code&gt;AllowAllTools: true&lt;/code&gt; in production. Use &lt;code&gt;AllowedTools&lt;/code&gt; whitelist to control available tools, use &lt;code&gt;DeniedTools&lt;/code&gt; blacklist to prohibit dangerous operations. This effectively prevents AI from executing dangerous commands—when it comes to security, being careful is always right.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Session management&lt;/strong&gt;: Requests with the same &lt;code&gt;sessionId&lt;/code&gt; automatically reuse sessions. Session state is persisted through &lt;code&gt;ProviderSessionId&lt;/code&gt;. Cancellation operations are passed through &lt;code&gt;CancellationTokenSource&lt;/code&gt;—good session management leads to good user experience.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagnostic output&lt;/strong&gt;: Incompatible CLI parameters generate &lt;code&gt;diagnostic&lt;/code&gt; type messages. Original SDK events are preserved as &lt;code&gt;event.raw&lt;/code&gt; type. Error messages include categorization (startup timeout, parameter incompatibility, etc.) for convenient troubleshooting—quick problem location provides some comfort.&lt;/p&gt;

&lt;h3&gt;
  
  
  Best Practices
&lt;/h3&gt;

&lt;p&gt;Based on our actual experience, here are some best practices—consider it a summary:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Use tool whitelists&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;AIRequest&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Prompt&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Analyze this file"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;AllowedTools&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"Read"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Grep"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Bash(git:*)"&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;Explicitly specify allowed tools through whitelisting to avoid AI performing unexpected operations. Especially for tools with write permissions (like Edit), extra caution is needed—after all, nobody wants to experience database deletion.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Set reasonable timeouts&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Timeout&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// 1 hour&lt;/span&gt;
&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StartupTimeout&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;60&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// 1 minute&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set appropriate timeout values based on task complexity. Too short may cause task interruption, too long may waste resources waiting for unresponsive requests—moderation in all things, excess is as bad as deficiency.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Enable session reuse&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SessionId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"my-session-123"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Setting the same sessionId for related tasks can reuse previous session context, improving response speed—context is sometimes quite important.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Handle streaming responses&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;StreamAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&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="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Type&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="n"&gt;StreamingChunkType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ThinkingDelta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;// Handle reasoning process&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="n"&gt;StreamingChunkType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ToolCallDelta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;// Handle tool call&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="n"&gt;StreamingChunkType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ContentDelta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;// Handle text output&lt;/span&gt;
            &lt;span class="k"&gt;break&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;Streaming responses can display AI processing progress in real-time, improving user experience. Especially for time-consuming tasks, real-time feedback is very important—watching progress is better than waiting blindly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Error handling and retry&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;StreamAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Handle response&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CopilotSessionException&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Handle session exception&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Copilot session failed"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// Decide whether to retry based on exception type&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Proper error handling and retry mechanisms can improve system stability—no one can guarantee programs never fail, being able to handle failures well is what matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;The upgrade from CLI invocation to SDK integration brought significant value to the HagiCode project—how should I put it, this upgrade was quite worthwhile:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Improved stability&lt;/strong&gt;: SDK provides more stable interfaces, unaffected by CLI version changes—no more worrying about version updates every day&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feature completeness&lt;/strong&gt;: Can capture complete event streams, including reasoning process and tool execution status—can see both process and result&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Development efficiency&lt;/strong&gt;: Type-safe SDK interfaces make development more efficient, reducing runtime errors—with type checking, peace of mind&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User experience&lt;/strong&gt;: Real-time event feedback lets users more clearly understand the AI's work process—knowing what it's thinking is better than knowing nothing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This upgrade is not just a technical solution replacement, but an optimization of the entire AI capability layer architecture. Through layered design and abstract interfaces, we gained better maintainability and extensibility—once architecture is done well, subsequent tasks become easier.&lt;/p&gt;

&lt;p&gt;If you're considering integrating GitHub Copilot into your .NET project, I hope the practical experiences in this article help you avoid some detours. The official SDK is indeed more stable and complete than CLI invocation, worth investing time to understand and master—after all, the right tools can make things事半功倍 (twice the result with half the effort), this saying isn't without reason.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/en/copilot" rel="noopener noreferrer"&gt;GitHub Copilot SDK Official Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/dotnet/orleans/" rel="noopener noreferrer"&gt;Orleans Distributed Framework&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/HagiCode-org/site" rel="noopener noreferrer"&gt;HagiCode Project GitHub Repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.hagicode.com" rel="noopener noreferrer"&gt;HagiCode Official Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/dotnet/core/extensions/dependency-injection-guidelines" rel="noopener noreferrer"&gt;.NET Dependency Injection Best Practices&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;If this article helped you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Give it a like to let more people see it—your support means a lot to us&lt;/li&gt;
&lt;li&gt;Come give us a Star on GitHub: &lt;a href="https://github.com/HagiCode-org/site" rel="noopener noreferrer"&gt;github.com/HagiCode-org/site&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Visit the official site to learn more: &lt;a href="https://hagicode.com" rel="noopener noreferrer"&gt;hagicode.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Watch the 30-minute practical demo: &lt;a href="https://www.bilibili.com/video/BV1pirZBuEzq/" rel="noopener noreferrer"&gt;www.bilibili.com/video/BV1pirZBuEzq/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;One-click install to experience: &lt;a href="https://docs.hagicode.com/installation/docker-compose" rel="noopener noreferrer"&gt;docs.hagicode.com/installation/docker-compose&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Desktop quick install: &lt;a href="https://hagicode.com/desktop/" rel="noopener noreferrer"&gt;hagicode.com/desktop/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Public beta has started, welcome to install and experience&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Writing this is about enough. Technical articles are never complete, after all technology evolves and we're always learning. If you have any questions or suggestions while using HagiCode, feel free to contact us anytime. Well, that's it for now...&lt;/p&gt;

&lt;h2&gt;
  
  
  Original Article &amp;amp; License
&lt;/h2&gt;

&lt;p&gt;Thanks for reading. If this article helped, consider liking, bookmarking, or sharing it.&lt;br&gt;
This article was created with AI assistance and reviewed by the author before publication.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Author: &lt;a href="https://www.newbe.pro" rel="noopener noreferrer"&gt;newbe36524&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Original URL: &lt;a href="https://docs.hagicode.com/go?platform=devto&amp;amp;target=%2Fblog%2F2026-04-03-github-copilot-sdk-integration%2F" rel="noopener noreferrer"&gt;https://docs.hagicode.com/go?platform=devto&amp;amp;target=%2Fblog%2F2026-04-03-github-copilot-sdk-integration%2F&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;License: Unless otherwise stated, this article is licensed under CC BY-NC-SA. Please retain attribution when sharing.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>githubcopilot</category>
      <category>net</category>
      <category>ai</category>
      <category>orleans</category>
    </item>
    <item>
      <title>The Hallucination Problem of AI Programming Assistants: How to Implement Specification-Driven Development with OpenSpec</title>
      <dc:creator>Hagicode</dc:creator>
      <pubDate>Thu, 02 Apr 2026 02:33:59 +0000</pubDate>
      <link>https://forem.com/newbe36524/the-hallucination-problem-of-ai-programming-assistants-how-to-implement-specification-driven-2i2j</link>
      <guid>https://forem.com/newbe36524/the-hallucination-problem-of-ai-programming-assistants-how-to-implement-specification-driven-2i2j</guid>
      <description>&lt;h1&gt;
  
  
  The Hallucination Problem of AI Programming Assistants: How to Implement Specification-Driven Development with OpenSpec
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;AI programming assistants are powerful, but they often generate code that doesn't meet actual requirements or violates project specifications. This article shares how the HagiCode project implements "specification-driven development" through the OpenSpec process, significantly reducing AI hallucination risks with a structured proposal mechanism.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;Anyone who has used GitHub Copilot or ChatGPT to write code has likely experienced this: the AI-generated code looks beautiful, but it's full of problems when actually used. It might use a component from the project incorrectly, ignore the team's coding standards, or write large chunks of logic based on non-existent assumptions.&lt;/p&gt;

&lt;p&gt;This is the so-called "AI hallucination" problem—in the programming domain, it manifests as generating code that appears reasonable but doesn't align with the actual situation of the project.&lt;/p&gt;

&lt;p&gt;Actually, this situation is quite frustrating. As AI programming assistants become more widespread, this problem becomes increasingly serious. After all, AI lacks understanding of project history, architectural decisions, and coding standards, while excessive freedom easily leads it to "creatively" generate code that doesn't match reality. This is quite similar to writing articles—without proper structure, it's easy to write something unfocused, when that's not actually the case.&lt;/p&gt;

&lt;p&gt;To address these pain points, we made a bold decision: instead of trying to make AI smarter, we put it in a "specification" cage. The changes brought by this decision may be greater than you imagine—I'll elaborate shortly.&lt;/p&gt;

&lt;h2&gt;
  
  
  About HagiCode
&lt;/h2&gt;

&lt;p&gt;The solution shared in this article comes from our practical experience in the &lt;a href="https://hagicode.com" rel="noopener noreferrer"&gt;HagiCode&lt;/a&gt; project. HagiCode is an open-source AI code assistant project dedicated to solving real-world problems in AI programming through structured engineering practices.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Root Causes of AI Hallucination
&lt;/h2&gt;

&lt;p&gt;Before diving into the solution, let's first look at where the problem actually lies. After all, know yourself and know your enemy, and you can fight a hundred battles with no danger of defeat—though applying this saying to AI is also somewhat interesting.&lt;/p&gt;

&lt;h3&gt;
  
  
  Missing Context
&lt;/h3&gt;

&lt;p&gt;AI models are trained on public codebases, but your project has its own history, conventions, and architectural decisions. This "tacit knowledge" cannot be directly accessed by AI, so the generated code often disconnects from the actual project situation.&lt;/p&gt;

&lt;p&gt;This can't be entirely blamed on AI—after all, it hasn't worked on your project, so how would it know your unwritten rules? It's like a new intern who doesn't understand the rules—that's normal, but the cost might be somewhat high.&lt;/p&gt;

&lt;h3&gt;
  
  
  Excessive Freedom
&lt;/h3&gt;

&lt;p&gt;When you ask AI to "help me implement a user authentication feature," it might generate code in any form. Without clear constraints, AI will implement according to what it "thinks" is reasonable, rather than according to your project's requirements.&lt;/p&gt;

&lt;p&gt;This is like letting someone who has never learned your project specifications freely express themselves—can problems be avoided? Actually, it's not that it's irresponsible, it just doesn't know what responsibility is.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lack of Verification
&lt;/h3&gt;

&lt;p&gt;After AI generates code, if there's no structured review process, code based on wrong assumptions will directly enter the codebase. By the time problems are discovered in testing or even production environments, the cost is too high.&lt;/p&gt;

&lt;p&gt;This is no different from locking the barn after the horse is stolen—except the horse has already run away, so what's the use of locking the barn? Everyone understands this principle, but actually doing it always feels troublesome. After all, before things go bad, who wants to spend extra time?&lt;/p&gt;

&lt;h2&gt;
  
  
  OpenSpec: The Answer to Specification-Driven Development
&lt;/h2&gt;

&lt;p&gt;HagiCode chose OpenSpec as the solution, with the core idea being: &lt;strong&gt;all code changes must go through a structured proposal process&lt;/strong&gt;, transforming abstract ideas into executable implementation plans.&lt;/p&gt;

&lt;p&gt;This sounds quite grand, but it essentially means having AI write the requirements document before writing the code. After all, preparedness ensures success, unpreparedness spells failure—the ancients didn't deceive us.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is OpenSpec
&lt;/h3&gt;

&lt;p&gt;OpenSpec is an npm-based command-line tool (&lt;code&gt;@fission-ai/openspec&lt;/code&gt;) that defines a standard proposal file structure and validation mechanism. Simply put, it requires AI to "write the requirements document first" before writing code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Three-Step Process to Prevent Hallucination
&lt;/h3&gt;

&lt;p&gt;OpenSpec ensures proposal quality through a three-step process:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Initialize proposal&lt;/strong&gt; - Set session state to Openspecing&lt;br&gt;
&lt;strong&gt;Step 2: Intermediate processing&lt;/strong&gt; - Maintain Openspecing state, gradually refine artifacts&lt;br&gt;
&lt;strong&gt;Step 3: Complete proposal&lt;/strong&gt; - Transition to Reviewing state&lt;/p&gt;

&lt;p&gt;This design has a clever detail: the first step uses the &lt;code&gt;ProposalGenerationStart&lt;/code&gt; type, and completion doesn't trigger a state transition. This ensures the entire multi-step process won't prematurely enter the review phase before completion.&lt;/p&gt;

&lt;p&gt;Actually, this detail is quite interesting—it's like cooking. If you lift the lid before the cooking time is reached, you definitely won't produce a good dish. Only by being patient, taking it step by step, can you finally create a good dish.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Implementation in the HagiCode project&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;MessageAssociationType&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ProposalGeneration&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ProposalExecution&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="c1"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;/// Marks the start of the three-step proposal generation process&lt;/span&gt;
    &lt;span class="c1"&gt;/// Does not transition to Reviewing state upon completion&lt;/span&gt;
    &lt;span class="c1"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;ProposalGenerationStart&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Standardized File Structure
&lt;/h3&gt;

&lt;p&gt;Every OpenSpec proposal follows the same 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;openspec/
├── changes/                    # Active and archived changes
│   ├── {change-name}/
│   │   ├── proposal.md        # Proposal description
│   │   ├── design.md          # Design document
│   │   ├── specs/             # Technical specifications
│   │   └── tasks.md           # Executable task list
│   └── archive/               # Archived changes
└── specs/                     # Independent specification library
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;According to statistics from the HagiCode project, there are currently 4000+ archived changes and 150,000+ lines of specification files. This historical accumulation not only gives AI rules to follow but also provides a valuable knowledge base for the team.&lt;/p&gt;

&lt;p&gt;This is like the classics left by ancients—read more and you'll naturally gain some insight. It's just that these classics aren't written on bamboo slips, but stored in files.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multi-Layer Validation Mechanism
&lt;/h3&gt;

&lt;p&gt;The system implements multi-layer validation to ensure proposal quality:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Validate required files exist&lt;/span&gt;
&lt;span class="nf"&gt;ValidateProposalFiles&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;// Validate execution prerequisites&lt;/span&gt;
&lt;span class="nf"&gt;ValidateExecuteAsync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;// Validate startup conditions&lt;/span&gt;
&lt;span class="nf"&gt;ValidateStartAsync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;// Validate archive conditions&lt;/span&gt;
&lt;span class="nf"&gt;ValidateArchiveAsync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;// Proposal name format validation (kebab-case)&lt;/span&gt;
&lt;span class="nf"&gt;ValidateNameFormat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These validations are like gatekeepers with layers of checks—only truly qualified proposals can pass. Although it seems cumbersome, it's better than letting terrible code enter the codebase, right?&lt;/p&gt;

&lt;h3&gt;
  
  
  Prompt Template Constraints
&lt;/h3&gt;

&lt;p&gt;AI execution in HagiCode uses predefined Handlebars templates that include clear step-by-step guidance and safeguards. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prohibit continuing without understanding user intent&lt;/li&gt;
&lt;li&gt;Prohibit generating unverified code&lt;/li&gt;
&lt;li&gt;Require re-providing when names are invalid&lt;/li&gt;
&lt;li&gt;If a change already exists, recommend using the continue command rather than recreating&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This approach of "dancing with shackles" actually makes AI more focused on understanding requirements and generating code that complies with specifications. Actually, constraints aren't necessarily a bad thing—after all, excessive freedom easily leads to chaos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practice: How to Use OpenSpec in Projects
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Installation and Initialization
&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; &lt;span class="nt"&gt;-g&lt;/span&gt; @fission-ai/openspec@1
openspec &lt;span class="nt"&gt;--version&lt;/span&gt;  &lt;span class="c"&gt;# Verify installation&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;openspec/&lt;/code&gt; folder structure will be automatically created in the project root directory.&lt;/p&gt;

&lt;p&gt;This step doesn't need much explanation—installing tools, everyone understands. Just remember to use the &lt;code&gt;@fission-ai/openspec@1&lt;/code&gt; version, as new versions might have pitfalls—after all, stability comes first.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating Proposals
&lt;/h3&gt;

&lt;p&gt;In HagiCode's conversation interface, use the shortcut command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/opsx:new
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or specify the change name and target repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/opsx:new "add-user-auth" --repos "repos/web"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Creating proposals is like outlining an article—once you have an outline, the rest is easier to write. It's just that many people like to start writing directly, only to realize halfway through that their thinking doesn't flow—that's truly a headache.&lt;/p&gt;

&lt;h3&gt;
  
  
  Generating Artifacts
&lt;/h3&gt;

&lt;p&gt;Use &lt;code&gt;/opsx:continue&lt;/code&gt; to gradually generate the required artifacts:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;proposal.md&lt;/strong&gt; - Describes the purpose and scope of the change&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Proposal: Add User Authentication&lt;/span&gt;

&lt;span class="gu"&gt;## Why&lt;/span&gt;
The current system lacks user authentication functionality and cannot protect sensitive APIs.

&lt;span class="gu"&gt;## What Changes&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Add JWT authentication middleware
&lt;span class="p"&gt;-&lt;/span&gt; Implement login/registration API
&lt;span class="p"&gt;-&lt;/span&gt; Update frontend integration
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;design.md&lt;/strong&gt; - Detailed technical design&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Design: Add User Authentication&lt;/span&gt;

&lt;span class="gu"&gt;## Context&lt;/span&gt;
Currently using public APIs accessible to anyone...

&lt;span class="gu"&gt;## Decisions&lt;/span&gt;
&lt;span class="p"&gt;1.&lt;/span&gt; Choose JWT over Session...
&lt;span class="p"&gt;2.&lt;/span&gt; Use HS256 algorithm...

&lt;span class="gu"&gt;## Risks&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Token leakage risk...
&lt;span class="p"&gt;-&lt;/span&gt; Mitigation measures...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;specs/&lt;/strong&gt; - Technical specifications and test scenarios&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# user-auth Specification&lt;/span&gt;

&lt;span class="gu"&gt;## Requirements&lt;/span&gt;

&lt;span class="gu"&gt;### Requirement: JWT Token Generation&lt;/span&gt;
The system SHALL use the HS256 algorithm to generate JWT tokens.

&lt;span class="gu"&gt;#### Scenario: Valid login&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; WHEN user provides valid credentials
&lt;span class="p"&gt;-&lt;/span&gt; THEN the system SHALL return a valid JWT token
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;tasks.md&lt;/strong&gt; - Executable task list&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Tasks: Add User Authentication&lt;/span&gt;

&lt;span class="gu"&gt;## 1. Backend Changes&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; [ ] 1.1 Create AuthController
&lt;span class="p"&gt;-&lt;/span&gt; [ ] 1.2 Implement JWT middleware
&lt;span class="p"&gt;-&lt;/span&gt; [ ] 1.3 Add unit tests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These artifacts are actually like drafts for writing an article—once the draft is written, the main text naturally flows smoothly. It's just that many people don't like writing drafts, thinking it wastes time, but actually drafts are where you can best clarify your thinking.&lt;/p&gt;

&lt;h3&gt;
  
  
  Review and Application
&lt;/h3&gt;

&lt;p&gt;After completing all artifacts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/opsx:apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;AI will read all context files and execute tasks step by step according to the checklist in tasks.md. At this point, because there are clear specifications, the quality of generated code will be much higher.&lt;/p&gt;

&lt;p&gt;Actually, by this step, things are already halfway done. With a clear task list, the rest is just methodical execution. It's just that many people skip the previous steps and come directly here, so quality naturally can't be guaranteed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Archiving
&lt;/h3&gt;

&lt;p&gt;After the change is complete:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/opsx:archive
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Move completed changes to the &lt;code&gt;archive/&lt;/code&gt; directory for future reference and reuse.&lt;/p&gt;

&lt;p&gt;Archiving is quite important—like properly storing a finished article. When you encounter similar problems in the future, flipping through previous records might give you answers. It's just that many people are too lazy to do it, thinking it's troublesome, but actually these accumulations are the most precious wealth.&lt;/p&gt;

&lt;h2&gt;
  
  
  Notes and Best Practices
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Proposal Naming Convention
&lt;/h3&gt;

&lt;p&gt;Use kebab-case format, starting with a letter, containing only lowercase letters, numbers, and hyphens:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ &lt;code&gt;add-user-auth&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;❌ &lt;code&gt;AddUserAuth&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;❌ &lt;code&gt;add--user-auth&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Naming conventions aren't a big deal, but being unified is generally good. After all, in code, consistency is important—it's just that many people don't pay attention to it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Avoiding Common Errors
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Using the wrong type in step 1 of the three-step process&lt;/strong&gt; - Will transition state prematurely&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Forgetting to trigger state transition in the final step&lt;/strong&gt; - Will get stuck in Openspecing state&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skipping review to execute directly&lt;/strong&gt; - Should first verify all artifacts are complete&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These errors are easily made by beginners—experienced people naturally know how to avoid them. It's just that beginners will eventually become experienced, and taking some detours is fine, I just hope they don't take too many.&lt;/p&gt;

&lt;h3&gt;
  
  
  Managing Multiple Changes
&lt;/h3&gt;

&lt;p&gt;OpenSpec supports managing multiple proposals simultaneously, which is particularly useful when handling large features:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# View all active changes&lt;/span&gt;
openspec list

&lt;span class="c"&gt;# Switch to a specific change&lt;/span&gt;
openspec apply &lt;span class="s2"&gt;"add-user-auth"&lt;/span&gt;

&lt;span class="c"&gt;# View change status&lt;/span&gt;
openspec status &lt;span class="nt"&gt;--change&lt;/span&gt; &lt;span class="s2"&gt;"add-user-auth"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Managing multiple changes is like writing several articles simultaneously—it requires some skill and patience. But you'll get used to it—after all, people can always adapt.&lt;/p&gt;

&lt;h3&gt;
  
  
  Understanding the Session State Machine
&lt;/h3&gt;

&lt;p&gt;Understanding state transitions helps troubleshoot problems:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Init → Drafting → Openspecing → Reviewing → Executing → ExecutionCompleted → Completed → Archived
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Openspecing&lt;/strong&gt;: Planning in progress&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reviewing&lt;/strong&gt;: Under review (can repeatedly modify artifacts)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Executing&lt;/strong&gt;: Executing (applying tasks.md)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;State machines are essentially a set of rules. Rules can be annoying sometimes, but more often they're useful. After all, nothing can be accomplished without norms—as the ancients said long ago.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Through the OpenSpec process, the HagiCode project has achieved significant results in addressing AI hallucination:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Reduce hallucinations&lt;/strong&gt; - AI must follow structured specifications and cannot generate code arbitrarily&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improve quality&lt;/strong&gt; - Multi-layer validation ensures changes meet project standards&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Accelerate collaboration&lt;/strong&gt; - Archived changes provide reference for future development&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Traceability&lt;/strong&gt; - Each change has complete proposal, design, specification, and task records&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This solution doesn't make AI smarter, but puts it in a "specification" cage. Practice has shown that dancing with shackles actually produces better dance.&lt;/p&gt;

&lt;p&gt;Actually, this principle is simple—constraints aren't necessarily a bad thing. It's like writing articles—with format constraints, it's actually easier to write good things. It's just that many people don't like constraints, thinking they limit their creativity, but actually creativity also needs soil to bloom and bear fruit.&lt;/p&gt;

&lt;p&gt;If you're also using AI programming assistants and have encountered similar problems, why not try OpenSpec? Specification-driven development may seem to add some steps, but this upfront investment will pay back many times over in code quality and maintenance efficiency.&lt;/p&gt;

&lt;p&gt;After all, sometimes being slower is actually being faster. It's just that many people don't understand this principle...&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;OpenSpec npm package: &lt;a href="https://www.npmjs.com/package/@fission-ai/openspec" rel="noopener noreferrer"&gt;www.npmjs.com/package/@fission-ai/openspec&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;HagiCode project repository: &lt;a href="https://github.com/HagiCode-org/site" rel="noopener noreferrer"&gt;github.com/HagiCode-org/site&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;HagiCode official website: &lt;a href="https://hagicode.com" rel="noopener noreferrer"&gt;hagicode.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Watch the 30-minute practical demo: &lt;a href="https://www.bilibili.com/video/BV1pirZBuEzq/" rel="noopener noreferrer"&gt;www.bilibili.com/video/BV1pirZBuEzq/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Docker Compose one-click installation: &lt;a href="https://docs.hagicode.com/installation/docker-compose" rel="noopener noreferrer"&gt;docs.hagicode.com/installation/docker-compose&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Desktop quick installation: &lt;a href="https://hagicode.com/desktop/" rel="noopener noreferrer"&gt;hagicode.com/desktop/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;If this article helps you, feel free to give a Star on GitHub. HagiCode public beta has begun—install now to participate in the experience.&lt;/p&gt;




&lt;p&gt;This article is pretty much done. There's nothing particularly profound, just a summary of some practical experience. Hope it's useful to everyone—after all, with sharing, you learn and let others learn too—it's a win-win, so why not?&lt;/p&gt;

&lt;p&gt;But articles are just articles—what's truly useful is practice. After all, what you get from paper is shallow—you must personally practice to truly understand, as the ancients said...&lt;/p&gt;

&lt;h2&gt;
  
  
  Original Article &amp;amp; License
&lt;/h2&gt;

&lt;p&gt;Thanks for reading. If this article helped, consider liking, bookmarking, or sharing it.&lt;br&gt;
This article was created with AI assistance and reviewed by the author before publication.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Author: &lt;a href="https://www.newbe.pro" rel="noopener noreferrer"&gt;newbe36524&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Original URL: &lt;a href="https://docs.hagicode.com/go?platform=devto&amp;amp;target=%2Fblog%2F2026-04-02-ai-coding-assistant-hallucination-openspec-spec-driven-development%2F" rel="noopener noreferrer"&gt;https://docs.hagicode.com/go?platform=devto&amp;amp;target=%2Fblog%2F2026-04-02-ai-coding-assistant-hallucination-openspec-spec-driven-development%2F&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;License: Unless otherwise stated, this article is licensed under CC BY-NC-SA. Please retain attribution when sharing.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>openspec</category>
      <category>hagicode</category>
    </item>
    <item>
      <title>Elegantly Implementing New User Onboarding in React: HagiCode's driver.js Practice</title>
      <dc:creator>Hagicode</dc:creator>
      <pubDate>Wed, 01 Apr 2026 07:37:53 +0000</pubDate>
      <link>https://forem.com/newbe36524/elegantly-implementing-new-user-onboarding-in-react-hagicodes-driverjs-practice-12de</link>
      <guid>https://forem.com/newbe36524/elegantly-implementing-new-user-onboarding-in-react-hagicodes-driverjs-practice-12de</guid>
      <description>&lt;h1&gt;
  
  
  Elegantly Implementing New User Onboarding in React: HagiCode's driver.js Practice
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;When users open your product for the first time, do they really know where to start? This article discusses our experience with driver.js for new user onboarding in the HagiCode project—just sharing some thoughts to spark discussion.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;Have you encountered this scenario: a new user signs up for your product, opens the page, and looks around confused—unsure where to click or what to do. As developers, we assume users will "explore on their own"—after all, human curiosity is limitless. But the reality is—most users will quietly leave within minutes because they can't find the entry point. The story begins abruptly, and ends just as naturally.&lt;/p&gt;

&lt;p&gt;New user onboarding is an important solution to this problem, though implementation isn't simple. A good onboarding system needs to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Accurately target and highlight page elements&lt;/li&gt;
&lt;li&gt;Support multi-step onboarding flows&lt;/li&gt;
&lt;li&gt;Remember user choices (complete/skip)&lt;/li&gt;
&lt;li&gt;Not affect page performance and normal interactions&lt;/li&gt;
&lt;li&gt;Have clear code structure for easy maintenance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;During HagiCode's development, we faced the same challenges. HagiCode is an AI coding assistant project with a core workflow of "user creates proposal → AI generates plan → user reviews → AI executes"—an OpenSpec process. For users new to this concept, this workflow is entirely new, so we needed good onboarding to help them get started quickly. After all, new things always take some time to get used to.&lt;/p&gt;

&lt;h2&gt;
  
  
  About HagiCode
&lt;/h2&gt;

&lt;p&gt;The solution shared in this article comes from our practical experience in the &lt;a href="https://hagicode.com" rel="noopener noreferrer"&gt;HagiCode&lt;/a&gt; project. HagiCode is a Claude-based AI coding assistant that helps developers complete code tasks more efficiently through the OpenSpec workflow. You can view our open-source code on &lt;a href="https://github.com/HagiCode-org/site" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Choose driver.js
&lt;/h2&gt;

&lt;p&gt;During the technology selection phase, we evaluated several mainstream onboarding libraries—each has its own characteristics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Intro.js&lt;/strong&gt;: Powerful but large in size, with relatively complex style customization&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shepherd.js&lt;/strong&gt;: Well-designed API, but a bit "heavy" for our scenario&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;driver.js&lt;/strong&gt;: Lightweight, concise, intuitive API, and supports React ecosystem&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We ultimately chose driver.js, mainly based on these considerations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Lightweight&lt;/strong&gt;: Small core library that won't significantly increase bundle size&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simple API&lt;/strong&gt;: Clear and intuitive configuration, quick to get started&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flexibility&lt;/strong&gt;: Supports custom positioning, styles, and interaction behaviors&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic Import&lt;/strong&gt;: Can be loaded on-demand without affecting first-screen performance&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When it comes to technology selection, there's no "best"—only "most suitable."&lt;/p&gt;

&lt;h2&gt;
  
  
  Technical Implementation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Core Configuration
&lt;/h3&gt;

&lt;p&gt;driver.js configuration is very straightforward. Here's the core configuration from the HagiCode project:&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;driver&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;driver.js&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;driver.js/dist/driver.css&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;newConversationDriver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;allowClose&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;// Allow users to close the onboarding&lt;/span&gt;
  &lt;span class="na"&gt;animate&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;// Enable animation effects&lt;/span&gt;
  &lt;span class="na"&gt;overlayClickBehavior&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;close&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Click overlay to close onboarding&lt;/span&gt;
  &lt;span class="na"&gt;disableActiveInteraction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Keep elements interactive&lt;/span&gt;
  &lt;span class="na"&gt;showProgress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="c1"&gt;// Don't show progress bar (we have custom progress management)&lt;/span&gt;
  &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;guideSteps&lt;/span&gt;           &lt;span class="c1"&gt;// Array of onboarding steps&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The considerations behind these configurations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;allowClose: true&lt;/code&gt; - Respect user choice, don't force completion of onboarding&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;disableActiveInteraction: false&lt;/code&gt; - Some steps require actual user interaction (like typing), so we can't disable interaction&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;overlayClickBehavior: 'close'&lt;/code&gt; - Give users a quick way to exit&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  State Management
&lt;/h3&gt;

&lt;p&gt;Persisting onboarding state is crucial—we don't want to re-onboard users every time they refresh the page. HagiCode uses localStorage to manage onboarding 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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;GuideState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pending&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;dismissed&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;completed&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="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;UserGuideState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GuideState&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;detailGuides&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="nx"&gt;GuideState&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;// Read state&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getUserGuideState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;UserGuideState&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;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;userGuideState&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="nx"&gt;state&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pending&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;detailGuides&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;// Update state&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;setUserGuideState&lt;/span&gt; &lt;span class="o"&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="nx"&gt;UserGuideState&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;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;userGuideState&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We defined three states:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;pending&lt;/code&gt;: Onboarding in progress, user hasn't completed or skipped yet&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;dismissed&lt;/code&gt;: User actively closed the onboarding&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;completed&lt;/code&gt;: User completed all steps&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For proposal detail page onboarding, we also support more fine-grained state tracking (through the &lt;code&gt;detailGuides&lt;/code&gt; dictionary), because a proposal may go through multiple stages (draft, review, execution complete), each requiring different onboarding. After all, the state of things is always changing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Target Element Positioning
&lt;/h3&gt;

&lt;p&gt;driver.js uses CSS selectors to locate target elements. HagiCode adopts a convention: using the &lt;code&gt;data-guide&lt;/code&gt; custom attribute to mark onboarding targets:&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;steps&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;element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-guide="launch"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;popover&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Start New Conversation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Click here to create a new conversation session...&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;Usage in components:&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;guide&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;launch&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleLaunch&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;New&lt;/span&gt; &lt;span class="nx"&gt;Conversation&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Benefits of this approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Avoids conflicts with business style class names&lt;/li&gt;
&lt;li&gt;Clear semantics—immediately apparent that this element relates to onboarding&lt;/li&gt;
&lt;li&gt;Easy to manage and maintain uniformly&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Dynamic Import Optimization
&lt;/h3&gt;

&lt;p&gt;Because onboarding functionality is only needed in specific scenarios (like new users visiting for the first time), we use dynamic import to optimize initial load performance:&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;initNewUserGuide&lt;/span&gt; &lt;span class="o"&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;// Dynamically import driver.js&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;driver&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="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;driver.js&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="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;driver.js/dist/driver.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Initialize onboarding&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newConversationDriver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="c1"&gt;// ...configuration&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;newConversationDriver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drive&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 way, driver.js and its styles are only loaded when needed, without affecting first-screen performance. After all, who wants to pay the waiting cost for something temporarily unused?&lt;/p&gt;

&lt;h2&gt;
  
  
  Onboarding Flow Design
&lt;/h2&gt;

&lt;p&gt;HagiCode implements two onboarding paths covering users' core usage scenarios.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conversation Onboarding (10 Steps)
&lt;/h3&gt;

&lt;p&gt;This onboarding helps users complete the entire flow from creating a conversation to submitting their first complete proposal:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;launch&lt;/strong&gt; - Start onboarding, introduce the "New Conversation" button&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;compose&lt;/strong&gt; - Guide user to type request in the input box&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;send&lt;/strong&gt; - Guide clicking the send button&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;proposal-launch-readme&lt;/strong&gt; - Guide creating a README proposal&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;proposal-compose-readme&lt;/strong&gt; - Guide editing README request content&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;proposal-submit-readme&lt;/strong&gt; - Guide submitting README proposal&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;proposal-launch-agents&lt;/strong&gt; - Guide creating an AGENTS.md proposal&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;proposal-compose-agents&lt;/strong&gt; - Guide editing AGENTS.md request&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;proposal-submit-agents&lt;/strong&gt; - Guide submitting AGENTS.md proposal&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;proposal-wait&lt;/strong&gt; - Explain that AI is processing, please wait&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The design philosophy behind this onboarding: through two actual proposal creation tasks (README and AGENTS.md), let users personally experience HagiCode's core workflow. After all, knowledge from books is shallow—true understanding comes from practice.&lt;/p&gt;

&lt;p&gt;The following images correspond to key nodes in the conversation onboarding:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqheqvuix5n1ewynu8g72.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqheqvuix5n1ewynu8g72.png" alt="Conversation onboarding: Starting from creating a normal conversation" width="800" height="493"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The first step of conversation onboarding first brings users to the "New Normal Conversation" entry point.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6kt8nmoqr055aeeqb3mz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6kt8nmoqr055aeeqb3mz.png" alt="Conversation onboarding: Type first request" width="800" height="358"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then guide users to write their first request in the input box, lowering the barrier for that first interaction.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3l9dtfyte6yj9alqir14.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3l9dtfyte6yj9alqir14.png" alt="Conversation onboarding: Send first message" width="800" height="358"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After input is complete, clearly prompt users to send their first message, making the operation path more coherent.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2eq7mzfb94fneihs20o4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2eq7mzfb94fneihs20o4.png" alt="Conversation onboarding: Wait for conversation list to continue execution" width="800" height="623"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When both proposals are created, the onboarding returns to the conversation list, letting users know they only need to wait for the system to continue executing and refresh.&lt;/p&gt;

&lt;h3&gt;
  
  
  Proposal Detail Onboarding (3 Steps)
&lt;/h3&gt;

&lt;p&gt;When users enter the proposal detail page, corresponding onboarding is triggered based on the proposal's current state:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;drafting&lt;/strong&gt; (Draft stage) - Guide users to view AI-generated plan&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;reviewing&lt;/strong&gt; (Review stage) - Guide users to execute the plan&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;executionCompleted&lt;/strong&gt; (Complete stage) - Guide users to archive the plan&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This onboarding's characteristic is state-driven—dynamically deciding which onboarding step to display based on the proposal's actual state. Things are always changing, so onboarding should change accordingly.&lt;/p&gt;

&lt;p&gt;The image below shows the proposal detail page's onboarding state during the "drafting stage":&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqiqm9qzi3gr1173jmor3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqiqm9qzi3gr1173jmor3.png" alt="Proposal detail onboarding: Generate plan first in drafting stage" width="800" height="650"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At this stage, the onboarding focuses user attention on the key action of "Generate Plan," avoiding confusion about what to do first when entering the detail page for the first time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Element Rendering Retry Mechanism
&lt;/h3&gt;

&lt;p&gt;In React applications, onboarding target elements may not have finished rendering yet (for example, waiting for asynchronous data loading). To handle this situation, HagiCode implements a retry mechanism:&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;waitForElement&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;selector&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="nx"&gt;maxRetries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;interval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&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;let&lt;/span&gt; &lt;span class="nx"&gt;retries&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="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&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;HTMLElement&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;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&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;checkElement&lt;/span&gt; &lt;span class="o"&gt;=&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;element&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;selector&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;HTMLElement&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;element&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;element&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;retries&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;maxRetries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;retries&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;checkElement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;interval&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="nf"&gt;reject&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;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Element not found: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;selector&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="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nf"&gt;checkElement&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;Call this function before initializing onboarding to ensure target elements exist. Sometimes, waiting a bit longer is worth it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Best Practices Summary
&lt;/h2&gt;

&lt;p&gt;Based on HagiCode's practical experience, here are several key best practices:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Onboarding Should Be "Escapable"
&lt;/h3&gt;

&lt;p&gt;Don't force users to complete onboarding. Some users are explorers who prefer to figure things out themselves. Provide a clear "Skip" button and remember their choice so you don't bother them next time. After all, beautiful things or people don't need to be possessed—appreciating their beauty from afar is enough.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Onboarding Content Should Be Concise and Powerful
&lt;/h3&gt;

&lt;p&gt;Each onboarding step should focus on a single goal:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Title&lt;/strong&gt;: Short and clear, no more than 10 characters&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Description&lt;/strong&gt;: Get straight to the point, tell users "what this is" and "why use it"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Avoid lengthy explanations—user attention during onboarding is very limited. Say too much, and no one will want to read it.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Selectors Should Be Stable
&lt;/h3&gt;

&lt;p&gt;Use stable element marking methods that don't change frequently. The &lt;code&gt;data-guide&lt;/code&gt; custom attribute is a good choice—avoid relying on class names or DOM structure, as these easily change during refactoring. Code is always changing, but some things should remain as stable as possible.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Test Your Onboarding
&lt;/h3&gt;

&lt;p&gt;HagiCode wrote complete test cases for the onboarding functionality:&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="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;NewUserConversationGuide&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should correctly initialize onboarding state&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="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;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getUserGuideState&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;expect&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="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pending&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;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should correctly update onboarding state&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setUserGuideState&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;completed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;detailGuides&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getUserGuideState&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;expect&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="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;completed&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;Testing ensures that when refactoring code, you won't accidentally break onboarding functionality. After all, no one wants to break existing functionality while making changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Performance Optimization
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Use dynamic imports to lazy load the onboarding library&lt;/li&gt;
&lt;li&gt;Avoid initializing onboarding logic after users have completed it&lt;/li&gt;
&lt;li&gt;Consider the performance impact of onboarding animations—can disable animations on low-end devices&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Performance, like life, should be conserved where it should be.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;New user onboarding is an important part of improving product user experience. In the HagiCode project, we used driver.js to build a complete onboarding system covering the entire workflow from conversation creation to proposal execution.&lt;/p&gt;

&lt;p&gt;Through this article, we hope to convey these core points:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Technology selection should match requirements&lt;/strong&gt;: driver.js isn't the most powerful, but it's the most suitable for us&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State management is crucial&lt;/strong&gt;: Use localStorage to persist onboarding state and avoid repeatedly bothering users&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Onboarding design should be focused&lt;/strong&gt;: Each step solves one problem, don't try to do too much&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code structure should be clear&lt;/strong&gt;: Separate onboarding configuration, state management, and UI logic for easy maintenance&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you're adding new user onboarding functionality to your project, I hope the practical experience shared in this article helps you. Actually, technology isn't that mysterious—try more, summarize more, and it'll get better over time...&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://driverjs.com/" rel="noopener noreferrer"&gt;driver.js Official Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/HagiCode-org/site" rel="noopener noreferrer"&gt;HagiCode Project Source Code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hagicode.com" rel="noopener noreferrer"&gt;HagiCode Official Website&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.hagicode.com" rel="noopener noreferrer"&gt;OpenSpec Workflow Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Original Article &amp;amp; License
&lt;/h2&gt;

&lt;p&gt;Thanks for reading. If this article helped, consider liking, bookmarking, or sharing it.&lt;br&gt;
This article was created with AI assistance and reviewed by the author before publication.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Author: &lt;a href="https://www.newbe.pro" rel="noopener noreferrer"&gt;newbe36524&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Original URL: &lt;a href="https://docs.hagicode.com/go?platform=devto&amp;amp;target=%2Fblog%2F2026-04-01-new-user-guide-with-driverjs%2F" rel="noopener noreferrer"&gt;https://docs.hagicode.com/go?platform=devto&amp;amp;target=%2Fblog%2F2026-04-01-new-user-guide-with-driverjs%2F&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;License: Unless otherwise stated, this article is licensed under CC BY-NC-SA. Please retain attribution when sharing.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>react</category>
      <category>driverjs</category>
    </item>
    <item>
      <title>Typing is Not as Good as Speaking, Speaking is Not as Good as Screenshots—Multimodal Input Practices for AI Code Assistants</title>
      <dc:creator>Hagicode</dc:creator>
      <pubDate>Tue, 31 Mar 2026 01:37:23 +0000</pubDate>
      <link>https://forem.com/newbe36524/typing-is-not-as-good-as-speaking-speaking-is-not-as-good-as-screenshots-multimodal-input-5adh</link>
      <guid>https://forem.com/newbe36524/typing-is-not-as-good-as-speaking-speaking-is-not-as-good-as-screenshots-multimodal-input-5adh</guid>
      <description>&lt;h1&gt;
  
  
  Typing is Not as Good as Speaking, Speaking is Not as Good as Screenshots—Multimodal Input Practices for AI Code Assistants
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;Actually, when it comes to writing code, there's an upper limit to how fast you can type. Sometimes something that can be said in a sentence requires banging on the keyboard for ages; sometimes a single image can explain clearly what would take piles of text to describe. This article discusses the experiences we encountered while building &lt;a href="https://hagicode.com" rel="noopener noreferrer"&gt;HagiCode&lt;/a&gt;—whether it's speech recognition or image upload, the goal is simply to make the AI code assistant a bit easier to use, that's all.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;While working on HagiCode, we discovered a problem—or rather, a problem that naturally emerged as users spent more time with it: relying solely on typing can be quite exhausting.&lt;/p&gt;

&lt;p&gt;Think about it: user interaction with the Agent is a core scenario. But having to sit at the keyboard clacking away every time... well, the efficiency isn't exactly high:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Typing is too slow&lt;/strong&gt;: Some complex problems—errors, interface issues—can take half a minute to type out, but might be spoken in ten seconds. That time difference is pretty frustrating.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Images are more direct&lt;/strong&gt;: Sometimes when the interface throws an error, or you want to compare with a design mock, or show code structure... the saying "a picture is worth a thousand words" may be old, but the truth remains. Letting AI "see" the problem directly is much clearer than describing it for ages.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Interaction should be natural&lt;/strong&gt;: Modern AI assistants should support text, voice, images and other methods, right? Users should be able to use whatever they want—that's what natural means, isn't it?&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So we thought, why not add speech recognition and image upload features to HagiCode, making Agent operations more convenient. After all, letting users type a few less characters is a good thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  About HagiCode
&lt;/h2&gt;

&lt;p&gt;The solutions shared in this article come from our practice in the &lt;a href="https://hagicode.com" rel="noopener noreferrer"&gt;HagiCode&lt;/a&gt; project—or rather, experiences discovered through constantly stepping into pits.&lt;/p&gt;

&lt;p&gt;HagiCode is an open-source AI code assistant project with a simple idea: use AI technology to improve development efficiency. As we built it, we discovered that users actually have quite strong demand for multimodal input—sometimes saying a sentence is faster than typing a pile of text, sometimes a single screenshot is clearer than describing for ages.&lt;/p&gt;

&lt;p&gt;These demands pushed us forward, and eventually we ended up with features like speech recognition and image upload. Users can interact with AI in the most natural way. It feels pretty good.&lt;/p&gt;

&lt;h2&gt;
  
  
  Analysis
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Technical Challenges of Speech Recognition
&lt;/h3&gt;

&lt;p&gt;When implementing the speech recognition feature, we encountered a tricky problem: &lt;strong&gt;the browser's WebSocket API doesn't support custom HTTP headers&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;And the speech recognition service we chose is ByteDance's Doubao speech recognition API. This API偏偏 insists on passing authentication information through HTTP headers—things like &lt;code&gt;accessToken&lt;/code&gt;, &lt;code&gt;secretKey&lt;/code&gt;, and such. Great, now we have a technical contradiction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Browser WebSocket API doesn't support the following approach&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ws&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WebSocket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;wss://api.com/ws&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;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Bearer token&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;  &lt;span class="c1"&gt;// Not supported&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 solutions before us basically came down to two:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;URL query parameter solution&lt;/strong&gt;: Put authentication info in the URL&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Advantage: simple to implement&lt;/li&gt;
&lt;li&gt;Disadvantage: credentials exposed on frontend, poor security; and some APIs strictly require header validation&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Backend proxy solution&lt;/strong&gt;: Implement WebSocket proxy on the backend&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Advantage: credentials stored securely on backend; fully compatible with API requirements&lt;/li&gt;
&lt;li&gt;Disadvantage: slightly more complex to implement&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Ultimately we chose the &lt;strong&gt;backend proxy solution&lt;/strong&gt;. After all, security is a bottom line that cannot be compromised—on this point, no one should try to fool anyone.&lt;/p&gt;

&lt;h3&gt;
  
  
  Image Upload Functional Requirements
&lt;/h3&gt;

&lt;p&gt;For the image upload feature, our requirements were actually quite simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Multiple upload methods&lt;/strong&gt;: click to select file, drag-and-drop upload, clipboard paste—gotta have them all, right?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;File validation&lt;/strong&gt;: type restrictions (PNG, JPG, WebP, GIF), size limits (5-10MB)—these are basic operations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User experience&lt;/strong&gt;: upload progress, preview, error prompts—people need to know what's happening&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security&lt;/strong&gt;: server-side validation, prevent malicious file uploads—this is a big deal&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Solutions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Speech Recognition: WebSocket Proxy Architecture
&lt;/h3&gt;

&lt;p&gt;We designed a three-layer architecture for speech recognition. How should I put it—we basically found a path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser WebSocket
       |
       | ws://backend/api/voice/ws
       | (binary audio)
       v
Backend Proxy
       |
       | wss://openspeech.bytedance.com/ (with auth header)
       v
Doubao API
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Core Component Implementation&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Frontend AudioWorklet Processor&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AudioProcessorWorklet&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;AudioWorkletProcessor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;outputs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;parameters&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;input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;inputs&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;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&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;// Resample to 16kHz (Doubao API requirement)&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;samples&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resampleAudio&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;48000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Accumulate samples to 500ms chunks&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;accumulatedSamples&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="nx"&gt;samples&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="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accumulatedSamples&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="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Convert to 16-bit PCM and send&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pcm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floatToPcm16&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;accumulatedSamples&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;port&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postMessage&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="s1"&gt;audioData&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="nx"&gt;pcm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;pcm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;buffer&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;accumulatedSamples&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="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Backend WebSocket Handler&lt;/strong&gt; (C#):
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;HttpGet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ws"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;GetWebSocket&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="n"&gt;HttpContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WebSockets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsWebSocketRequest&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="n"&gt;_webSocketHandler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;HandleAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HttpContext&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;ol&gt;
&lt;li&gt;
&lt;strong&gt;Frontend VoiceTextArea Component&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;VoiceTextArea&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;forwardRef&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HTMLTextAreaElement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;VoiceTextAreaProps&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="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onTextRecognized&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;maxDuration&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;ref&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;isRecording&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;interimText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;volume&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;startRecording&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stopRecording&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="nf"&gt;useVoiceRecording&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;onTextRecognized&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;maxDuration&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"flex gap-2"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Voice button */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleButtonClick&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isRecording&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;VolumeWaveform&lt;/span&gt; &lt;span class="na"&gt;volume&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;volume&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Mic&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Text input */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;textarea&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;displayValue&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleChange&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Image Upload: Multi-Method Upload Component
&lt;/h3&gt;

&lt;p&gt;We built a fully-featured image upload component that supports all three upload methods. How should I put it—we basically covered all the common user scenarios.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Core Features&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Three Upload Methods&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Click upload&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleClick&lt;/span&gt; &lt;span class="o"&gt;=&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;fileInputRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Drag-and-drop upload&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleDrop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DragEvent&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;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataTransfer&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="mi"&gt;0&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;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;uploadFile&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="c1"&gt;// Clipboard paste&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handlePaste&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ClipboardEvent&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;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;item&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clipboardData&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;items&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&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="s1"&gt;image/&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;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAsFile&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;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;uploadFile&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Frontend Validation&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;validateFile&lt;/span&gt; &lt;span class="o"&gt;=&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="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="nl"&gt;valid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;error&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;=&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;acceptedTypes&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="nx"&gt;file&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="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;valid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Only PNG, JPG, JPEG, WebP, and GIF images are allowed&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="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;maxSize&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;valid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Maximum file size is &lt;/span&gt;&lt;span class="p"&gt;${(&lt;/span&gt;&lt;span class="nx"&gt;maxSize&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;MB`&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;valid&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="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Backend Upload Handler&lt;/strong&gt; (TypeScript):
&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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Route&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createFileRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/upload&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)({&lt;/span&gt;
  &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;handlers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;POST&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;request&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;formData&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;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;formData&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="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;file&lt;/span&gt;&lt;span class="dl"&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;File&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// Validate&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;validation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;validateFile&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;validation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isValid&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;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;validation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Save file&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;uuid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;uuidv4&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="o"&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;uploadDir&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;uuid&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;extension&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;writeFile&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="nx"&gt;buffer&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;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`/uploaded/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;today&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;uuid&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;extension&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="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;
  
  
  Practice Guide
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How to Use Speech Recognition
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Configure speech recognition service&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to speech recognition settings page&lt;/li&gt;
&lt;li&gt;Configure Doubao Speech's &lt;code&gt;AppId&lt;/code&gt; and &lt;code&gt;AccessToken&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;(Optional) Configure hot words to improve recognition accuracy for technical terms&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Use in input field&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click the microphone icon on the left side of the input field&lt;/li&gt;
&lt;li&gt;Start speaking when you see the waveform animation&lt;/li&gt;
&lt;li&gt;Click the icon again to stop recording&lt;/li&gt;
&lt;li&gt;Recognition results will be automatically inserted at the cursor position&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Hot Word Configuration Example&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 plaintext"&gt;&lt;code&gt;TypeScript
React
useState
useEffect
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  How to Use Image Upload
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Upload methods&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click upload button to select file&lt;/li&gt;
&lt;li&gt;Drag image directly to upload area&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;Ctrl+V&lt;/code&gt; to paste screenshot from clipboard&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Supported formats&lt;/strong&gt;: PNG, JPG, JPEG, WebP, GIF&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Size limit&lt;/strong&gt;: Default 5MB (configurable)&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Notes
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Speech recognition&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Requires microphone permission&lt;/li&gt;
&lt;li&gt;Recommended for use in quiet environments&lt;/li&gt;
&lt;li&gt;Maximum recording duration supported is 300 seconds (configurable)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Image upload&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only supports common image formats&lt;/li&gt;
&lt;li&gt;Note file size limits&lt;/li&gt;
&lt;li&gt;Uploaded images automatically generate preview URLs&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Security considerations&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Speech recognition credentials stored on backend&lt;/li&gt;
&lt;li&gt;Image upload has strict server-side validation&lt;/li&gt;
&lt;li&gt;Production environment recommends using HTTPS/WSS&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;After adding speech recognition and image upload, HagiCode's user experience has indeed improved significantly. Users can now interact with AI in more natural ways—speaking instead of typing, screenshots instead of describing. How should I put it... it's like finally finding a more comfortable way to communicate.&lt;/p&gt;

&lt;p&gt;When building this feature, we encountered the browser WebSocket issue of not supporting custom headers, and ultimately solved it through the backend proxy solution. This solution not only ensures security but also lays the foundation for integrating other WebSocket services that require authentication in the future—I guess that's an unexpected bonus.&lt;/p&gt;

&lt;p&gt;The image upload component is similar too—using multiple upload methods lets users choose the most convenient one for their scenario. Whether clicking, dragging, or pasting directly, uploads can be completed quickly. All roads lead to Rome, it's just that some roads are easier to travel, others are a bit more winding.&lt;/p&gt;

&lt;p&gt;"Typing is not as good as speaking, speaking is not as good as screenshots"—this saying fits quite well here. If you're also building similar AI assistant products, I hope these experiences can help you, even if just a little.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/HagiCode-org/site" rel="noopener noreferrer"&gt;HagiCode GitHub Repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hagicode.com" rel="noopener noreferrer"&gt;HagiCode Official Website&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.volcengine.com/docs/6561/79829" rel="noopener noreferrer"&gt;Doubao Speech Recognition API Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API" rel="noopener noreferrer"&gt;Web Audio API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSocket" rel="noopener noreferrer"&gt;WebSocket API&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;If this article helps you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Give it a like to help more people see it&lt;/li&gt;
&lt;li&gt;Come to GitHub and give us a Star: &lt;a href="https://github.com/HagiCode-org/site" rel="noopener noreferrer"&gt;github.com/HagiCode-org/site&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Visit the official website to learn more: &lt;a href="https://hagicode.com" rel="noopener noreferrer"&gt;hagicode.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Watch the 30-minute practical demo: &lt;a href="https://www.bilibili.com/video/BV1pirZBuEzq/" rel="noopener noreferrer"&gt;www.bilibili.com/video/BV1pirZBuEzq/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;One-click install and experience: &lt;a href="https://docs.hagicode.com/installation/docker-compose" rel="noopener noreferrer"&gt;docs.hagicode.com/installation/docker-compose&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Desktop quick install: &lt;a href="https://hagicode.com/desktop/" rel="noopener noreferrer"&gt;hagicode.com/desktop/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Public beta has started, welcome to install and experience&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Original Article &amp;amp; License
&lt;/h2&gt;

&lt;p&gt;Thanks for reading. If this article helped, consider liking, bookmarking, or sharing it.&lt;br&gt;
This article was created with AI assistance and reviewed by the author before publication.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Author: &lt;a href="https://www.newbe.pro" rel="noopener noreferrer"&gt;newbe36524&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Original URL: &lt;a href="https://docs.hagicode.com/go?platform=devto&amp;amp;target=%2Fblog%2F2026-03-31-voice-and-image-upload-multimodal-input%2F" rel="noopener noreferrer"&gt;https://docs.hagicode.com/go?platform=devto&amp;amp;target=%2Fblog%2F2026-03-31-voice-and-image-upload-multimodal-input%2F&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;License: Unless otherwise stated, this article is licensed under CC BY-NC-SA. Please retain attribution when sharing.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>websocket</category>
    </item>
    <item>
      <title>GLM-5.1 Full Support and Gemini CLI Integration: HagiCode's Multi-Model Evolution</title>
      <dc:creator>Hagicode</dc:creator>
      <pubDate>Mon, 30 Mar 2026 01:57:32 +0000</pubDate>
      <link>https://forem.com/newbe36524/glm-51-full-support-and-gemini-cli-integration-hagicodes-multi-model-evolution-3mki</link>
      <guid>https://forem.com/newbe36524/glm-51-full-support-and-gemini-cli-integration-hagicodes-multi-model-evolution-3mki</guid>
      <description>&lt;h1&gt;
  
  
  GLM-5.1 Full Support and Gemini CLI Integration: HagiCode's Multi-Model Evolution
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;This article introduces recent important updates to the HagiCode platform—comprehensive support for Zhipu AI's GLM-5.1 model, and the successful integration of Gemini CLI as the tenth Agent CLI. These two updates further strengthen the platform's multi-model capabilities and multi-CLI ecosystem.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;Time flies, and the development of large language models grows like bamboo in spring—shooting upward rapidly. Once we cheered for "an AI that can write code," but now we're in an era of multi-model collaboration and multi-tool integration. Is this interesting? Perhaps, after all, what developers need has never been just tools themselves, but a kind of composure that can adapt to different scenarios and switch flexibly.&lt;/p&gt;

&lt;p&gt;As an AI-assisted coding platform, HagiCode has recently welcomed two major events: first, the comprehensive integration of Zhipu AI's GLM-5.1 model, and second, Gemini CLI officially becoming the tenth supported Agent CLI. These events are neither big nor small, but for the platform's improvement, they're certainly a good thing.&lt;/p&gt;

&lt;p&gt;GLM-5.1 is Zhipu AI's latest flagship model. Compared to GLM-5.0, it has stronger reasoning capabilities, deeper code understanding, and smoother tool calling. More importantly, it's the first GLM model to support image input—what does this mean? It means users can directly take screenshots for the AI to see problems, without struggling to describe them. If you've used it, you understand this convenience.&lt;/p&gt;

&lt;p&gt;At the same time, through the HagiCode.Libs.Providers architecture, HagiCode has successfully integrated Gemini CLI. This is the tenth Agent CLI, and honestly, reaching this point brings some sense of accomplishment.&lt;/p&gt;

&lt;p&gt;It's worth mentioning that HagiCode's image upload feature allows users to communicate directly with AI through screenshots. Even when running GLM 4.7, the platform runs well and has already helped complete many important construction projects for the project. As for GLM-5.1? Naturally, it will go even further.&lt;/p&gt;

&lt;h2&gt;
  
  
  About HagiCode
&lt;/h2&gt;

&lt;p&gt;The solution shared in this article comes from our practical experience in the &lt;a href="https://hagicode.com" rel="noopener noreferrer"&gt;HagiCode&lt;/a&gt; project. HagiCode is an open-source AI-assisted coding platform, designed to provide developers with a flexible and powerful AI programming assistant through multi-model and multi-CLI architecture design. Project address: &lt;a href="https://github.com/HagiCode-org/site" rel="noopener noreferrer"&gt;github.com/HagiCode-org/site&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Multi-CLI Architecture Design
&lt;/h2&gt;

&lt;p&gt;One of HagiCode's core advantages is supporting multiple different AI programming CLI tools through a unified abstraction layer. The benefits of this design, when you get down to it, are just: new things can come in, old things can stay, and the code doesn't get messy. After all, everyone hopes life could be like this, right?&lt;/p&gt;

&lt;h3&gt;
  
  
  AIProviderType Enumeration
&lt;/h3&gt;

&lt;p&gt;The platform defines supported CLI provider types through the &lt;code&gt;AIProviderType&lt;/code&gt; enumeration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;AIProviderType&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ClaudeCodeCli&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="c1"&gt;// Claude Code CLI&lt;/span&gt;
    &lt;span class="n"&gt;CodexCli&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;// GitHub Copilot Codex&lt;/span&gt;
    &lt;span class="n"&gt;GitHubCopilot&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="c1"&gt;// GitHub Copilot&lt;/span&gt;
    &lt;span class="n"&gt;CodebuddyCli&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="c1"&gt;// Codebuddy CLI&lt;/span&gt;
    &lt;span class="n"&gt;OpenCodeCli&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;// OpenCode CLI&lt;/span&gt;
    &lt;span class="n"&gt;IFlowCli&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;         &lt;span class="c1"&gt;// IFlow CLI&lt;/span&gt;
    &lt;span class="n"&gt;HermesCli&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="c1"&gt;// Hermes CLI&lt;/span&gt;
    &lt;span class="n"&gt;QoderCli&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;         &lt;span class="c1"&gt;// Qoder CLI&lt;/span&gt;
    &lt;span class="n"&gt;KiroCli&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;// Kiro CLI&lt;/span&gt;
    &lt;span class="n"&gt;KimiCli&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;// Kimi CLI&lt;/span&gt;
    &lt;span class="n"&gt;GeminiCli&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="c1"&gt;// Gemini CLI (newly added)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, Gemini CLI has joined this family as the tenth member. Each CLI has unique characteristics and applicable scenarios, and users can flexibly choose according to their needs. After all, all roads lead to Rome—some roads are just easier to walk, others a bit more winding.&lt;/p&gt;

&lt;h3&gt;
  
  
  Provider Architecture
&lt;/h3&gt;

&lt;p&gt;HagiCode.Libs.Providers provides a unified Provider interface, making the integration of each CLI standardized and concise. Taking Gemini CLI as an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GeminiProvider&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ICliProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;GeminiOptions&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;DefaultExecutableCandidates&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"gemini"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"gemini-cli"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;ManagedBootstrapArgument&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"--acp"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;"gemini"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;IsAvailable&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_executableResolver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ResolveFirstAvailablePath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DefaultExecutableCandidates&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&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 benefits of this design are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;New CLI integration only requires implementing one Provider class&lt;/li&gt;
&lt;li&gt;Unified lifecycle management and session pooling&lt;/li&gt;
&lt;li&gt;Automated alias resolution and executable file lookup&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When you get down to it, this design just simplifies complex things and makes life a little easier.&lt;/p&gt;

&lt;h3&gt;
  
  
  Provider Registry
&lt;/h3&gt;

&lt;p&gt;The Provider Registry automatically handles alias mapping and registration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;provider&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="n"&gt;GeminiProvider&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"gemini-cli"&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="k"&gt;continue&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 means users can call Gemini CLI using either &lt;code&gt;gemini&lt;/code&gt; or &lt;code&gt;gemini-cli&lt;/code&gt;, and the system will automatically recognize it. It's like having many friends—some call you by your formal name, some by your nickname, but either way, it's you.&lt;/p&gt;

&lt;h2&gt;
  
  
  GLM-5.1 Model Support
&lt;/h2&gt;

&lt;p&gt;GLM-5.1 is Zhipu AI's latest flagship model, and HagiCode has completed comprehensive support for it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Secondary Professions Catalog
&lt;/h3&gt;

&lt;p&gt;HagiCode manages all supported models through the Secondary Professions Catalog. Here's the configuration for the GLM series:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model ID&lt;/th&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;SupportsImage&lt;/th&gt;
&lt;th&gt;Compatible CLI Families&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;glm-4.7&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;GLM 4.7&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;claude, codebuddy, hermes, qoder, kiro&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;glm-5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;GLM 5&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;claude, codebuddy, hermes, qoder, kiro&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;glm-5-turbo&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;GLM 5 Turbo&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;claude, codebuddy, hermes, qoder, kiro&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;glm-5.0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;GLM 5.0 (Legacy)&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;claude, codebuddy, hermes, qoder, kiro&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;glm-5.1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;GLM 5.1&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;true&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;claude, codebuddy, hermes, qoder, kiro&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The key features of GLM-5.1 can be summarized as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Independent version identifier, without legacy baggage&lt;/li&gt;
&lt;li&gt;First GLM model to support image input&lt;/li&gt;
&lt;li&gt;Stronger reasoning capabilities and code understanding&lt;/li&gt;
&lt;li&gt;Extensive multi-CLI compatibility&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  GLM-5.1 vs GLM-5.0
&lt;/h3&gt;

&lt;p&gt;From a code perspective, the key differences between GLM-5.1 and GLM-5.0:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// GLM-5.0 (Legacy) - has special retention logic&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Glm50CodebuddySecondaryProfessionId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"secondary-glm-5-codebuddy"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Glm50CodebuddyModelValue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"glm-5.0"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// GLM-5.1 - independent new model identifier&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Glm51SecondaryProfessionId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"secondary-glm-5-1"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Glm51ModelValue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"glm-5.1"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;GLM-5.0 carries a "Legacy" mark, which is an old version identifier retained for backward compatibility. GLM-5.1 is a completely new independent version without any historical baggage. It's like some people always live in the past, while others travel light and walk faster.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuring GLM-5.1
&lt;/h3&gt;

&lt;p&gt;Example configuration for using GLM-5.1 in HagiCode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"primaryProfessionId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"profession-claude-code"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"secondaryProfessionId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"secondary-glm-5-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"glm-5.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reasoning"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"high"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Image Upload Feature
&lt;/h2&gt;

&lt;p&gt;HagiCode's image support is implemented through the &lt;code&gt;SupportsImage&lt;/code&gt; property of SecondaryProfession:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HeroSecondaryProfessionSettingDto&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;SupportsImage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&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 the Secondary Professions Catalog, the configuration for GLM-5.1 is as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"secondary-glm-5-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"supportsImage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means users can directly upload screenshots for AI analysis, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Screenshots of error messages&lt;/li&gt;
&lt;li&gt;UI interface issues&lt;/li&gt;
&lt;li&gt;Data visualization charts&lt;/li&gt;
&lt;li&gt;Code execution results&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No need to manually describe problems—just take a screenshot. Once you use this feature, you'll understand its convenience. After all, for some things, seeing is better than hearing a thousand descriptions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gemini CLI Integration
&lt;/h2&gt;

&lt;p&gt;As the tenth Agent CLI, Gemini CLI is integrated into HagiCode through the standard Provider architecture.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuration Options
&lt;/h3&gt;

&lt;p&gt;Gemini CLI supports rich configuration options:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GeminiOptions&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;ExecutablePath&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;WorkingDirectory&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;SessionId&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;Model&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;AuthenticationMethod&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;AuthenticationToken&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;AuthenticationInfo&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;EnvironmentVariables&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;ExtraArguments&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;StartupTimeout&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;CliPoolSettings&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;PoolSettings&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&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;These options cover everything from basic configuration to advanced features, allowing users to flexibly configure according to their needs. After all, everyone's needs are different, and having flexibility is always good.&lt;/p&gt;

&lt;h3&gt;
  
  
  ACP Communication Protocol
&lt;/h3&gt;

&lt;p&gt;Gemini CLI supports the ACP (Agent Communication Protocol), which is HagiCode's unified CLI communication standard. Through ACP, different CLIs can interact with the platform in a consistent manner, greatly simplifying integration work. When you get down to it, it's just unifying complex things so everyone can work a little easier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Environment Configuration
&lt;/h2&gt;

&lt;p&gt;To use Zhipu AI's models, you need to configure the corresponding environment variables.&lt;/p&gt;

&lt;h3&gt;
  
  
  Zhipu AI ZAI Platform
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANTHROPIC_AUTH_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your-zai-api-key"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANTHROPIC_BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://open.bigmodel.cn/api/anthropic"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Alibaba Cloud DashScope
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANTHROPIC_AUTH_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your-aliyun-api-key"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANTHROPIC_BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://coding.dashscope.aliyuncs.com/apps/anthropic"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After configuration is complete, HagiCode can call the GLM-5.1 model normally. This task is neither difficult nor simple—just follow the steps.&lt;/p&gt;

&lt;h2&gt;
  
  
  HagiCode's Own Build Practice
&lt;/h2&gt;

&lt;p&gt;Speaking of practice, the best example is HagiCode platform's own build process. HagiCode's development has already fully utilized AI capabilities:&lt;/p&gt;

&lt;h3&gt;
  
  
  Runs Well with GLM 4.7
&lt;/h3&gt;

&lt;p&gt;HagiCode platform is well-optimized, so even using GLM 4.7 provides a good development experience. The platform has helped complete several important build projects, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multi-CLI Provider integration&lt;/li&gt;
&lt;li&gt;Image upload feature implementation&lt;/li&gt;
&lt;li&gt;Documentation generation and content publishing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is actually quite good—after all, not everyone needs to use the latest things. What suits you is best.&lt;/p&gt;

&lt;h3&gt;
  
  
  GLM-5.1 for Greater Efficiency
&lt;/h3&gt;

&lt;p&gt;After upgrading to GLM-5.1, these capabilities will be further enhanced:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stronger code understanding, reducing back-and-forth communication&lt;/li&gt;
&lt;li&gt;More accurate dependency analysis, pointing in the right direction in one go&lt;/li&gt;
&lt;li&gt;More efficient error diagnosis, quickly locating problems&lt;/li&gt;
&lt;li&gt;Image input support, accelerating problem description&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's like upgrading from a bicycle to a car—you can reach the same destinations, just with different speed and comfort.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multi-CLI Integration Best Practices
&lt;/h2&gt;

&lt;p&gt;HagiCode.Libs.Providers provides a unified registration and usage mechanism:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddHagiCodeLibs&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;gemini&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serviceProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ICliProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;GeminiOptions&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;();&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;codebuddy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serviceProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ICliProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CodebuddyOptions&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;();&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;hermes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serviceProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ICliProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;HermesOptions&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This dependency injection design makes using each CLI very concise, and also facilitates unit testing and mocking. After all, writing clean code is also a form of responsibility to yourself.&lt;/p&gt;

&lt;h3&gt;
  
  
  Notes
&lt;/h3&gt;

&lt;p&gt;In actual use, there are a few things to note:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;API Key Configuration&lt;/strong&gt;: Ensure &lt;code&gt;ANTHROPIC_AUTH_TOKEN&lt;/code&gt; is correctly set, otherwise the model cannot be called&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model Availability&lt;/strong&gt;: GLM-5.1 requires enabling permissions at the corresponding model provider&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Image Feature&lt;/strong&gt;: Only models with &lt;code&gt;supportsImage: true&lt;/code&gt; can use the image upload feature&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CLI Installation&lt;/strong&gt;: Before using Gemini CLI, ensure &lt;code&gt;gemini&lt;/code&gt; or &lt;code&gt;gemini-cli&lt;/code&gt; is in the system PATH&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These are small things, but if small things aren't handled well, they can become big things. So pay attention to them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Through comprehensive support for GLM-5.1 and successful integration of Gemini CLI, HagiCode has further strengthened its capabilities as a multi-model, multi-CLI AI programming platform. These updates not only provide users with more choices but also demonstrate HagiCode's forward-looking and extensible architecture design.&lt;/p&gt;

&lt;p&gt;GLM-5.1's image support capability, combined with HagiCode's screenshot upload feature, makes "speaking through pictures" possible—greatly reducing the cost of problem description. And support for ten CLIs means users can flexibly choose the most suitable AI programming assistant based on their preferences and scenarios. After all, having more choices is always a good thing.&lt;/p&gt;

&lt;p&gt;Most importantly, HagiCode platform's own build practice proves: even using GLM 4.7, the platform can run well and complete complex tasks; after upgrading to GLM-5.1, development efficiency will be further improved. It's like life—you don't necessarily have to pursue the best, what suits you is good. Of course, if you can become better while still being suitable, that's naturally even better.&lt;/p&gt;

&lt;p&gt;If you're interested in a multi-model, multi-CLI AI programming platform, why not try HagiCode—open-source, free, and constantly evolving. Trying doesn't cost anything, and it might really suit you.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/HagiCode-org/site" rel="noopener noreferrer"&gt;HagiCode GitHub Repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hagicode.com" rel="noopener noreferrer"&gt;HagiCode Official Website&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://open.bigmodel.cn/" rel="noopener noreferrer"&gt;Zhipu AI Open Platform&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/google/gemini-cli" rel="noopener noreferrer"&gt;Gemini CLI Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.hagicode.com/installation/docker-compose" rel="noopener noreferrer"&gt;Docker Compose Quick Installation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hagicode.com/desktop/" rel="noopener noreferrer"&gt;Desktop Installation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;If this article helped you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Give a Star on GitHub: &lt;a href="https://github.com/HagiCode-org/site" rel="noopener noreferrer"&gt;github.com/HagiCode-org/site&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Visit the official website to learn more: &lt;a href="https://hagicode.com" rel="noopener noreferrer"&gt;hagicode.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Watch a 30-minute practical demo: &lt;a href="https://www.bilibili.com/video/BV1pirZBuEzq/" rel="noopener noreferrer"&gt;www.bilibili.com/video/BV1pirZBuEzq/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;One-click installation experience: &lt;a href="https://docs.hagicode.com/installation/docker-compose" rel="noopener noreferrer"&gt;docs.hagicode.com/installation/docker-compose&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Public beta has started, welcome to install and experience&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Original Article &amp;amp; License
&lt;/h2&gt;

&lt;p&gt;Thanks for reading. If this article helped, consider liking, bookmarking, or sharing it.&lt;br&gt;
This article was created with AI assistance and reviewed by the author before publication.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Author: &lt;a href="https://www.newbe.pro" rel="noopener noreferrer"&gt;newbe36524&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Original URL: &lt;a href="https://docs.hagicode.com/go?platform=devto&amp;amp;target=%2Fblog%2F2026-03-30-hagicode-glm-5-1-gemini-cli-update%2F" rel="noopener noreferrer"&gt;https://docs.hagicode.com/go?platform=devto&amp;amp;target=%2Fblog%2F2026-03-30-hagicode-glm-5-1-gemini-cli-update%2F&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;License: Unless otherwise stated, this article is licensed under CC BY-NC-SA. Please retain attribution when sharing.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>hagicode</category>
      <category>gemini</category>
      <category>ai</category>
      <category>glm</category>
    </item>
    <item>
      <title>Technical Analysis of the HagiCode Soul Platform: From Concept to Independent Platform</title>
      <dc:creator>Hagicode</dc:creator>
      <pubDate>Sun, 29 Mar 2026 13:20:14 +0000</pubDate>
      <link>https://forem.com/newbe36524/technical-analysis-of-the-hagicode-soul-platform-from-concept-to-independent-platform-2col</link>
      <guid>https://forem.com/newbe36524/technical-analysis-of-the-hagicode-soul-platform-from-concept-to-independent-platform-2col</guid>
      <description>&lt;h1&gt;
  
  
  Technical Analysis of the HagiCode Soul Platform: From Concept to Independent Platform
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;Writing technical articles isn't really a big deal—it's just organizing the pits you've fallen into and the detours you've taken. After all, who hasn't been young? This article will dive deep into the design philosophy, architectural evolution, and core technical implementation of the Soul (AI Agent Personality Configuration System) in the HagiCode project, exploring how to provide a more focused experience for creating and sharing Agent personalities through an independent platform.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;In the practice of AI Agent development, we often encounter a seemingly simple yet extremely important question: &lt;strong&gt;How can different Agents have stable and unique language styles and personality traits?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This question is quite frustrating. In the early Hero system of HagiCode, different heroes (Agent instances) mainly relied on class configurations and generic prompts to distinguish their expression styles. This approach brought some obvious pain points—perhaps those who have done this can relate.&lt;/p&gt;

&lt;p&gt;First, language styles are difficult to maintain consistently. For the same "software engineer" role, today's response might be professional and rigorous, while tomorrow's output becomes casual and scattered. This isn't a problem with the model itself, but rather the lack of an independent personality configuration layer to constrain and guide the output style.&lt;/p&gt;

&lt;p&gt;Second, the sense of character is generally weak. When we describe an Agent's characteristics, we can often only use vague adjectives like "friendly," "professional," or "humorous," without specific language rules to support these abstract descriptions. Simply put, it sounds nice but there's no way to actually implement it.&lt;/p&gt;

&lt;p&gt;Third, the reusability of personality configurations is nearly zero. Suppose we carefully design a "cat maid waitress" speaking style and want to reuse this expression in another business scenario—we almost need to configure from scratch. Beautiful things or people don't have to be possessed, we just want to reuse them... but it's really difficult.&lt;/p&gt;

&lt;p&gt;It was precisely to solve these practical problems that we introduced the &lt;strong&gt;Soul&lt;/strong&gt; mechanism—a &lt;strong&gt;language style configuration layer&lt;/strong&gt; independent of equipment and descriptions. Soul can define an Agent's speaking habits, tone preferences, and word choice boundaries, can be shared and reused across multiple heroes, and can be automatically injected into system prompts when a Session is first called.&lt;/p&gt;

&lt;p&gt;Some might think this is nothing special—just configuring a few prompts, right? But sometimes, the key to a problem isn't whether it can be done, but how to do it more elegantly. As Soul capabilities gradually matured, we realized it had the potential for independent development. A dedicated Soul platform could allow users to more focusedly create, share, and browse various interesting personality configurations without being distracted by other features of the Hero system. Thus, the &lt;strong&gt;soul.hagicode.com&lt;/strong&gt; independent platform was born.&lt;/p&gt;

&lt;h2&gt;
  
  
  About HagiCode
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://hagicode.com" rel="noopener noreferrer"&gt;HagiCode&lt;/a&gt; is an open-source AI code assistant project built with a modern tech stack, dedicated to providing developers with a smooth intelligent programming experience. The Soul platform solution shared in this article is exactly the practical experience we explored during HagiCode development to solve the practical problem of Agent personality management. If you find this solution valuable, it shows we've accumulated certain technical judgment in engineering practice—then the HagiCode project itself is worth understanding.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/HagiCode-org/site" rel="noopener noreferrer"&gt;github.com/HagiCode-org/site&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Official Site: &lt;a href="https://hagicode.com" rel="noopener noreferrer"&gt;hagicode.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Video Demo: &lt;a href="https://www.bilibili.com/video/BV1pirZBuEzq/" rel="noopener noreferrer"&gt;www.bilibili.com/video/BV1pirZBuEzq/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Desktop Quick Install: &lt;a href="https://hagicode.com/desktop/" rel="noopener noreferrer"&gt;hagicode.com/desktop/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Technical Architecture Evolution of the Soul Platform
&lt;/h2&gt;

&lt;p&gt;The development of the Soul Platform didn't happen overnight, but went through three clear stages. This story started suddenly and ended naturally.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 1: Hero-Embedded Soul Configuration
&lt;/h3&gt;

&lt;p&gt;The earliest Soul implementation existed as a functional module within the Hero workspace. We added an independent SOUL editing area in the Hero interface, supporting both preset application and text fine-tuning.&lt;/p&gt;

&lt;p&gt;Preset application allowed users to select from classic personality templates, such as "Professional Software Engineer" or "Cat Maid Waitress." Text fine-tuning let users make personalized modifications based on presets. The backend Hero entity correspondingly added a &lt;code&gt;Soul&lt;/code&gt; field and identified the source through &lt;code&gt;SoulCatalogId&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This phase solved the "is it there" problem, and it was still a child, growing through stumbles. But as Soul content became richer, the architecture coupled with the Hero system began to show limitations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 2: In-Site Marketplace
&lt;/h3&gt;

&lt;p&gt;To provide better Soul discovery and reuse experience, we built a SOUL Marketplace directory page, supporting browsing, searching, detail viewing, and favoriting.&lt;/p&gt;

&lt;p&gt;In this phase, we introduced a combination design of &lt;strong&gt;50 main Catalogs (base characters)&lt;/strong&gt; and &lt;strong&gt;10 orthogonal rules (expression styles)&lt;/strong&gt;. The main Catalog defines the Agent's core persona, such as abstract character settings like "Mistport Traveler" or "Night Hunter"; orthogonal rules define the expression methods, such as language style features like "Concise &amp;amp; Professional" or "Verbose &amp;amp; Friendly".&lt;/p&gt;

&lt;p&gt;50 × 10 = 500 combination possibilities, providing users with a rich personality configuration space. This number isn't much, but isn't little either—how should I put it, all roads lead to Rome, some roads are just easier to walk. The backend generates a complete SOUL catalog through &lt;code&gt;catalog-sources.json&lt;/code&gt;, while the frontend is responsible for presenting these catalog items as an interactive card list.&lt;/p&gt;

&lt;p&gt;The in-site Marketplace was a good transition solution, but still just a transition. It still depended on the main system, and for users who only wanted to use Soul functionality, the access path was still too deep. After all, who wants to go in a big circle just to do something simple?&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 3: Independent Platform Split
&lt;/h3&gt;

&lt;p&gt;Ultimately, we decided to migrate Soul capabilities to an independent repository (&lt;code&gt;repos/soul&lt;/code&gt;), changing the original main system's Marketplace to external jump guidance. The new platform adopts a &lt;strong&gt;Builder-first&lt;/strong&gt; design philosophy—the default homepage is the creation workspace, where users can start creating their own personality configurations the moment they open the website.&lt;/p&gt;

&lt;p&gt;The tech stack in this phase also underwent a comprehensive upgrade: adopting the Vite 8 + React 19 + TypeScript 5.9 combination, using the shadcn/ui component system to unify design language, and introducing Tailwind CSS 4's theme variable system. The improvement in frontend engineering level laid a solid foundation for subsequent feature iterations.&lt;/p&gt;

&lt;p&gt;Everything faded... no, everything is just beginning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Core Technical Design and Implementation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Material Integration Strategy
&lt;/h3&gt;

&lt;p&gt;A core design philosophy of the Soul platform is &lt;strong&gt;local-first&lt;/strong&gt;. This means the homepage must be fully operable without a backend, and remote material failures must not block page entry.&lt;/p&gt;

&lt;p&gt;Actually, this isn't a big deal—just considering one more step when designing the system. Local snapshots serve as the baseline, remote serves as enhancement, and this approach allows the product to provide basic usability under any network conditions. Specifically, we adopted a two-layer material architecture:&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;loadBuilderMaterials&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;BuilderMaterials&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;localMaterials&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createLocalMaterials&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// Local baseline&lt;/span&gt;

  &lt;span class="k"&gt;try&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;inspirationFragments&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;fetchMarketplaceItems&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;// Remote enhancement&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;localMaterials&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;inspirationFragments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;remoteState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ready&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;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&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="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;localMaterials&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;remoteState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fallback&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;  &lt;span class="c1"&gt;// Graceful degradation&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;Local materials come from build-time snapshots of the main system documentation, containing complete data for 50 base characters and 10 expression rules. Remote materials come from user-published Souls, obtained through the Marketplace API. The combination of both provides users with a complete material spectrum from official templates to community creativity. Wanting to laugh to disguise the tears you shed... no, actually it's nothing, just local plus remote.&lt;/p&gt;

&lt;h3&gt;
  
  
  Soul Fragment Data Model
&lt;/h3&gt;

&lt;p&gt;The core data abstraction of Soul is &lt;strong&gt;SoulFragment&lt;/strong&gt; (Soul Fragment):&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;SoulFragment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;fragmentId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;main-catalog&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;expression-rule&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;published-soul&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="na"&gt;keywords&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="nx"&gt;localized&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nb"&gt;Partial&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="nx"&gt;AppLocale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;LocalizedFragmentContent&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;
  &lt;span class="na"&gt;sourceRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SoulFragmentSourceRef&lt;/span&gt;
  &lt;span class="na"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SoulFragmentMeta&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;group&lt;/code&gt; field distinguishes fragment types: main catalog defines character core, orthogonal rules define expression methods, and user-published Souls are marked as &lt;code&gt;published-soul&lt;/code&gt;. The &lt;code&gt;localized&lt;/code&gt; field supports multiple languages, allowing the same fragment to present different titles and descriptions in different language environments. Internationalization design should be done early—we've also applied this principle.&lt;/p&gt;

&lt;p&gt;Builder draft state encapsulates the user's current editing 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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;SoulBuilderDraft&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;draftId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="na"&gt;selectedMainFragmentId&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;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
  &lt;span class="na"&gt;selectedRuleFragmentId&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;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
  &lt;span class="na"&gt;inspirationSoulId&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;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
  &lt;span class="na"&gt;mainSlotText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="na"&gt;ruleSlotText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="na"&gt;customPrompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="na"&gt;previewText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="na"&gt;updatedAt&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each fragment selected by the user in the editor has its content spliced into the corresponding slot, forming the final preview text. &lt;code&gt;mainSlotText&lt;/code&gt; corresponds to main character content, &lt;code&gt;ruleSlotText&lt;/code&gt; corresponds to expression rule content, and &lt;code&gt;customPrompt&lt;/code&gt; is the user's additional supplementary instructions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Preview Compilation Mechanism
&lt;/h3&gt;

&lt;p&gt;Preview compilation is the core function of Soul Builder, assembling the fragments selected by the user and custom text into a copyable system prompt:&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;function&lt;/span&gt; &lt;span class="nf"&gt;compilePreview&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;:&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;SoulBuilderDraft&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mainSlotText&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;ruleSlotText&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;customPrompt&lt;/span&gt;&lt;span class="dl"&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;fragments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;mainFragment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SoulFragment&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
    &lt;span class="nx"&gt;ruleFragment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SoulFragment&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
    &lt;span class="nx"&gt;inspirationFragment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SoulFragment&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;PreviewCompilation&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Assembly logic: main character + expression rule + inspiration reference + custom content&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The compilation result is displayed in the central preview panel, where users can see the final effect in real-time and copy it to the clipboard with one click. This function is quite simple, isn't it? But simple things are often the most practical.&lt;/p&gt;

&lt;h3&gt;
  
  
  Frontend State Management
&lt;/h3&gt;

&lt;p&gt;The frontend state management of Soul Builder follows an important principle: &lt;strong&gt;clear division of state boundaries&lt;/strong&gt;. Specifically, drawer state is not persisted and not directly written to drafts; only explicit Builder operations trigger state changes.&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;// Domain state (useSoulBuilder)&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;useSoulBuilder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Material loading and caching&lt;/span&gt;
  &lt;span class="c1"&gt;// Slot aggregation and preview compilation&lt;/span&gt;
  &lt;span class="c1"&gt;// Copy behavior and feedback messages&lt;/span&gt;
  &lt;span class="c1"&gt;// Locale-safe descriptors&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Presentation state (useHomeEditorState)&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;useHomeEditorState&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// activeSlot, drawerSide, drawerOpen&lt;/span&gt;
  &lt;span class="c1"&gt;// Default focus behavior&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This separation ensures the safety of editing state and the responsiveness of the interface. Opening and closing the drawer is pure UI interaction and doesn't need to trigger complex persistence logic. This is just stating the obvious! No, actually it's very important—interface state and business state should be clearly distinguished to avoid UI interaction polluting the core data model.&lt;/p&gt;

&lt;h3&gt;
  
  
  Single Drawer Lifecycle
&lt;/h3&gt;

&lt;p&gt;Soul Builder adopts a single drawer mode: only one slot drawer can be open at a time. Clicking the overlay, pressing ESC, or switching slots automatically closes the current drawer. This design simplifies state management and aligns with common mobile drawer interaction patterns.&lt;/p&gt;

&lt;p&gt;Closing the drawer doesn't clear the current editing content—when users switch back, their context is preserved. This "lightweight" drawer design avoids interrupting user operations. After all, who wants to lose their hard-written content because of an accidental click?&lt;/p&gt;

&lt;h3&gt;
  
  
  Bilingual Support Architecture
&lt;/h3&gt;

&lt;p&gt;Internationalization is an important feature of the Soul platform. System copy fully supports bilingual switching, while user draft text is never rewritten due to language switching—because draft text itself is user-entered content and doesn't involve system translation.&lt;/p&gt;

&lt;p&gt;Official inspiration cards (Marketplace Soul) maintain their upstream display names but provide best-effort English summaries. For Souls with Chinese names, we generate English versions through predefined mapping rules:&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;// Main character English name mapping&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mainNameEnglishMap&lt;/span&gt; &lt;span class="o"&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;雾港旅人&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;Mistport Traveler&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;夜航猎手&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;Night Hunter&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;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Orthogonal rule English name mapping&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ruleNameEnglishMap&lt;/span&gt; &lt;span class="o"&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;简洁干练&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;Concise &amp;amp; Professional&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;啰嗦亲切&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;Verbose &amp;amp; Friendly&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;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This mapping table looks quite simple, but maintaining it well takes considerable thought. After all, with 50 main characters and 10 orthogonal rules, multiplied together that's 500 combinations—not large, not small.&lt;/p&gt;

&lt;h2&gt;
  
  
  Backend Catalog Generation
&lt;/h2&gt;

&lt;p&gt;Batch generation of Soul Catalog is completed on the backend, using C# to implement the automated creation of 50 × 10 = 500 combinations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MainCatalogs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;orthogonal&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OrthogonalCatalogs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;catalogId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$"soul-&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;-&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;orthogonal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;00&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;displayName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;BuildNickname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;orthogonal&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;soulSnapshot&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;BuildSoulSnapshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;orthogonal&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;// Write to database...&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 nickname generation algorithm combines main character names with expression rule names to create imaginative Agent aliases:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;MainHandleRoots&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s"&gt;"雾港"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"夜航"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"零帧"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"星渊"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"霓虹"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&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;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;OrthogonalHandleSuffixes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s"&gt;"旅人"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"猎手"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"术师"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"行者"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&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;// Combination examples: 雾港旅人, 夜航猎手, 零帧术师...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Soul snapshot assembly follows a fixed template format, combining main character core, signature features, expression rule core, and output constraints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;BuildSoulSnapshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;orthogonal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;string&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="sc"&gt;'\n'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s"&gt;$"你的人设内核来自「&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;」：&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Core&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;$"保持以下标志性语言特征：&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Signature&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;$"你的表达规则来自「&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;orthogonal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;」：&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;orthogonal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Core&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;$"必须遵循这些输出约束：&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;orthogonal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Signature&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&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 template assembly is boring work, but without these boring tasks, where would interesting products come from?&lt;/p&gt;

&lt;h2&gt;
  
  
  Platform Migration Strategy
&lt;/h2&gt;

&lt;p&gt;After splitting Soul from the main system to an independent platform, we faced an important challenge: how to handle existing user data. This is a common problem—splitting is easy, migration is hard. We took three safeguards:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backward Compatibility Guarantee&lt;/strong&gt;. Saved Hero SOUL snapshots remain visible; historical snapshots can still be previewed even if they lose their Marketplace source ID. This means users' previous configurations won't be lost—only the display location has changed. After all, no one wants their hard-earned configurations to suddenly disappear.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Main System API Deprecation&lt;/strong&gt;. The in-site Marketplace API returns a 410 Gone status code with migration guidance, directing users to visit soul.hagicode.com.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hero SOUL Form Modification&lt;/strong&gt;. A migration notice section is added to the Hero Soul editing area, explicitly informing users that the Soul platform is now independent and providing a one-click jump button:&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;// HeroSoulForm.tsx&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="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rounded-2xl border border-orange-200/70 bg-orange-50/80 p-4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;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="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hero.soul.migrationTitle&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;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="o"&gt;&amp;lt;&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="p"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hero.soul.migrationDescription&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;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onOpenSoulPlatform&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="nf"&gt;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hero.soul.openSoulPlatformAction&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;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Button&lt;/span&gt;&lt;span class="err"&gt;&amp;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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Practical Takeaways
&lt;/h2&gt;

&lt;p&gt;Looking back at the entire development process of the Soul platform, there are several practical experiences worth sharing. These are just some insights from someone who's been through it—not grand principles, just pits fallen into.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Local-first runtime assumptions&lt;/strong&gt;. When designing features that depend on remote data, always assume the network might be unavailable. Local snapshots as baseline, remote as enhancement—this approach allows the product to provide basic usability under any network conditions. After all, these days, network can cut out anytime, who can say for sure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Clear division of state boundaries&lt;/strong&gt;. Interface state and business state should be clearly distinguished to avoid UI interaction polluting the core data model. Drawer toggle is pure UI state and doesn't need to be mixed with draft persistence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Internationalization design should be done early&lt;/strong&gt;. If your product has internationalization needs, it's best to consider it during the data model design phase. The &lt;code&gt;localized&lt;/code&gt; field, although increasing data structure complexity, greatly reduces the cost of maintaining multilingual content later.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Material synchronization workflow should be automated&lt;/strong&gt;. The Soul platform's local materials come from the main system documentation. When upstream documentation updates, there needs to be a mechanism to sync to the frontend snapshot. We designed the &lt;code&gt;npm run materials:sync&lt;/code&gt; script to automate this process, ensuring materials always stay consistent with upstream.&lt;/p&gt;

&lt;h2&gt;
  
  
  Future Outlook
&lt;/h2&gt;

&lt;p&gt;Based on the current architecture design, the Soul platform could consider the following development directions. These are just some preliminary ideas, not necessarily correct—just throwing out bricks to attract jade.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Community sharing ecosystem&lt;/strong&gt;. Support user upload and sharing of custom Souls, add rating, commenting, and recommendation mechanisms, allowing excellent Soul configurations to be discovered and used by more people. After all, shared joy is double joy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multimodal expansion&lt;/strong&gt;. Beyond text style, could also consider supporting voice style configuration, emoji usage preferences, code style and formatting rules, and other dimensions. This sounds nice in theory, but in practice might be...&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Intelligent assistance&lt;/strong&gt;. Automatic Soul recommendations based on usage scenarios, style transfer and fusion, even A/B testing different Souls' actual effects. Why care if it's sunny or cloudy for beauty? Just try it and see.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cross-platform synchronization&lt;/strong&gt;. Support importing personality configurations from other AI platforms, provide standardized Soul export formats, integrate with mainstream Agent frameworks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;This article shared the complete evolution process of the HagiCode Soul platform from concept to independent platform. We explored why the Soul mechanism is needed (solving Agent personality consistency issues), analyzed the three development stages of the technical architecture (embedded configuration, in-site Marketplace, independent platform), deeply explained core data models, state management, preview compilation, and internationalization design, and shared practical experience with platform migration.&lt;/p&gt;

&lt;p&gt;The essence of Soul is a &lt;strong&gt;personality configuration layer independent of business logic&lt;/strong&gt;. It makes AI Agent language styles definable, reusable, and shareable. From a technical perspective, this design isn't complex, but the problem it solves is real and widely needed.&lt;/p&gt;

&lt;p&gt;If you're also developing an AI Agent product, consider whether your personality configuration solution is flexible enough. The Soul platform practice might provide some inspiration.&lt;/p&gt;

&lt;p&gt;This feeling could become a memory, but at that time I was already lost. Perhaps one day, you'll encounter similar problems, and when that time comes, if this article can help a little, that's enough.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;HagiCode Official Site: &lt;a href="https://hagicode.com" rel="noopener noreferrer"&gt;hagicode.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Soul Platform: &lt;a href="https://soul.hagicode.com" rel="noopener noreferrer"&gt;soul.hagicode.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;HagiCode GitHub: &lt;a href="https://github.com/HagiCode-org/site" rel="noopener noreferrer"&gt;github.com/HagiCode-org/site&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;HagiCode Desktop: &lt;a href="https://hagicode.com/desktop/" rel="noopener noreferrer"&gt;hagicode.com/desktop/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;HagiCode Installation Docs: &lt;a href="https://docs.hagicode.com/installation/docker-compose" rel="noopener noreferrer"&gt;docs.hagicode.com/installation/docker-compose&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;If you find this article helpful, feel free to give the project a Star on GitHub. Public beta has begun, welcome to install and experience.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  原文与版权说明
&lt;/h2&gt;

&lt;p&gt;感谢您的阅读,如果您觉得本文有用,欢迎点赞、收藏和分享支持。&lt;br&gt;
本内容采用人工智能辅助协作,最终内容由作者审核并确认。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;本文作者: &lt;a href="https://www.newbe.pro" rel="noopener noreferrer"&gt;newbe36524&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;原文链接: &lt;a href="https://docs.hagicode.com/go?platform=devto&amp;amp;target=%2Fblog%2F2026-03-25-hagicode-soul-platform-technical-analysis%2F" rel="noopener noreferrer"&gt;https://docs.hagicode.com/go?platform=devto&amp;amp;target=%2Fblog%2F2026-03-25-hagicode-soul-platform-technical-analysis%2F&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
&amp;lt;!-- hagicode-copyright:end --&amp;gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>hagicode</category>
      <category>soul</category>
      <category>agents</category>
    </item>
  </channel>
</rss>
