Technical Analysis of the HagiCode Soul Platform: From Concept to Independent Platform
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.
Background
In the practice of AI Agent development, we often encounter a seemingly simple yet extremely important question: How can different Agents have stable and unique language styles and personality traits?
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.
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.
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.
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.
It was precisely to solve these practical problems that we introduced the Soul mechanism—a language style configuration layer 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.
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 soul.hagicode.com independent platform was born.
About HagiCode
HagiCode 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.
- GitHub: github.com/HagiCode-org/site
- Official Site: hagicode.com
- Video Demo: www.bilibili.com/video/BV1pirZBuEzq/
- Desktop Quick Install: hagicode.com/desktop/
Technical Architecture Evolution of the Soul Platform
The development of the Soul Platform didn't happen overnight, but went through three clear stages. This story started suddenly and ended naturally.
Phase 1: Hero-Embedded Soul Configuration
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.
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 Soul field and identified the source through SoulCatalogId.
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.
Phase 2: In-Site Marketplace
To provide better Soul discovery and reuse experience, we built a SOUL Marketplace directory page, supporting browsing, searching, detail viewing, and favoriting.
In this phase, we introduced a combination design of 50 main Catalogs (base characters) and 10 orthogonal rules (expression styles). 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 & Professional" or "Verbose & Friendly".
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 catalog-sources.json, while the frontend is responsible for presenting these catalog items as an interactive card list.
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?
Phase 3: Independent Platform Split
Ultimately, we decided to migrate Soul capabilities to an independent repository (repos/soul), changing the original main system's Marketplace to external jump guidance. The new platform adopts a Builder-first design philosophy—the default homepage is the creation workspace, where users can start creating their own personality configurations the moment they open the website.
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.
Everything faded... no, everything is just beginning.
Core Technical Design and Implementation
Material Integration Strategy
A core design philosophy of the Soul platform is local-first. This means the homepage must be fully operable without a backend, and remote material failures must not block page entry.
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:
export async function loadBuilderMaterials(): Promise<BuilderMaterials> {
const localMaterials = createLocalMaterials(snapshot) // Local baseline
try {
const inspirationFragments = await fetchMarketplaceItems() // Remote enhancement
return { ...localMaterials, inspirationFragments, remoteState: "ready" }
} catch (error) {
return { ...localMaterials, remoteState: "fallback" } // Graceful degradation
}
}
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.
Soul Fragment Data Model
The core data abstraction of Soul is SoulFragment (Soul Fragment):
export type SoulFragment = {
fragmentId: string
group: "main-catalog" | "expression-rule" | "published-soul"
title: string
summary: string
content: string
keywords: string[]
localized?: Partial<Record<AppLocale, LocalizedFragmentContent>>
sourceRef: SoulFragmentSourceRef
meta: SoulFragmentMeta
}
The group field distinguishes fragment types: main catalog defines character core, orthogonal rules define expression methods, and user-published Souls are marked as published-soul. The localized 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.
Builder draft state encapsulates the user's current editing state:
export type SoulBuilderDraft = {
draftId: string
name: string
selectedMainFragmentId: string | null
selectedRuleFragmentId: string | null
inspirationSoulId: string | null
mainSlotText: string
ruleSlotText: string
customPrompt: string
previewText: string
updatedAt: string
}
Each fragment selected by the user in the editor has its content spliced into the corresponding slot, forming the final preview text. mainSlotText corresponds to main character content, ruleSlotText corresponds to expression rule content, and customPrompt is the user's additional supplementary instructions.
Preview Compilation Mechanism
Preview compilation is the core function of Soul Builder, assembling the fragments selected by the user and custom text into a copyable system prompt:
export function compilePreview(
draft: Pick<SoulBuilderDraft, "mainSlotText" | "ruleSlotText" | "customPrompt">,
fragments: {
mainFragment: SoulFragment | null
ruleFragment: SoulFragment | null
inspirationFragment: SoulFragment | null
}
): PreviewCompilation {
// Assembly logic: main character + expression rule + inspiration reference + custom content
}
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.
Frontend State Management
The frontend state management of Soul Builder follows an important principle: clear division of state boundaries. Specifically, drawer state is not persisted and not directly written to drafts; only explicit Builder operations trigger state changes.
// Domain state (useSoulBuilder)
export function useSoulBuilder() {
// Material loading and caching
// Slot aggregation and preview compilation
// Copy behavior and feedback messages
// Locale-safe descriptors
}
// Presentation state (useHomeEditorState)
export function useHomeEditorState() {
// activeSlot, drawerSide, drawerOpen
// Default focus behavior
}
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.
Single Drawer Lifecycle
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.
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?
Bilingual Support Architecture
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.
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:
// Main character English name mapping
const mainNameEnglishMap = {
"雾港旅人": "Mistport Traveler",
"夜航猎手": "Night Hunter",
// ...
}
// Orthogonal rule English name mapping
const ruleNameEnglishMap = {
"简洁干练": "Concise & Professional",
"啰嗦亲切": "Verbose & Friendly",
// ...
}
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.
Backend Catalog Generation
Batch generation of Soul Catalog is completed on the backend, using C# to implement the automated creation of 50 × 10 = 500 combinations:
foreach (var main in source.MainCatalogs)
{
foreach (var orthogonal in source.OrthogonalCatalogs)
{
var catalogId = $"soul-{main.Index:00}-{orthogonal.Index:00}";
var displayName = BuildNickname(main, orthogonal);
var soulSnapshot = BuildSoulSnapshot(main, orthogonal);
// Write to database...
}
}
The nickname generation algorithm combines main character names with expression rule names to create imaginative Agent aliases:
private static readonly string[] MainHandleRoots = [
"雾港", "夜航", "零帧", "星渊", "霓虹", "断云", ...
];
private static readonly string[] OrthogonalHandleSuffixes = [
"旅人", "猎手", "术师", "行者", "星使", ...
];
// Combination examples: 雾港旅人, 夜航猎手, 零帧术师...
Soul snapshot assembly follows a fixed template format, combining main character core, signature features, expression rule core, and output constraints:
private static string BuildSoulSnapshot(main, orthogonal) => string.Join('\n', [
$"你的人设内核来自「{main.Name}」:{main.Core}",
$"保持以下标志性语言特征:{main.Signature}",
$"你的表达规则来自「{orthogonal.Name}」:{orthogonal.Core}",
$"必须遵循这些输出约束:{orthogonal.Signature}"
]);
This template assembly is boring work, but without these boring tasks, where would interesting products come from?
Platform Migration Strategy
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:
Backward Compatibility Guarantee. 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.
Main System API Deprecation. The in-site Marketplace API returns a 410 Gone status code with migration guidance, directing users to visit soul.hagicode.com.
Hero SOUL Form Modification. 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:
// HeroSoulForm.tsx
<div className="rounded-2xl border border-orange-200/70 bg-orange-50/80 p-4">
<div>{t('hero.soul.migrationTitle')}</div>
<p>{t('hero.soul.migrationDescription')}</p>
<Button onClick={onOpenSoulPlatform}>
{t('hero.soul.openSoulPlatformAction')}
</Button>
</div>
Practical Takeaways
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.
Local-first runtime assumptions. 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.
Clear division of state boundaries. 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.
Internationalization design should be done early. If your product has internationalization needs, it's best to consider it during the data model design phase. The localized field, although increasing data structure complexity, greatly reduces the cost of maintaining multilingual content later.
Material synchronization workflow should be automated. 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 npm run materials:sync script to automate this process, ensuring materials always stay consistent with upstream.
Future Outlook
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.
Community sharing ecosystem. 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.
Multimodal expansion. 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...
Intelligent assistance. 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.
Cross-platform synchronization. Support importing personality configurations from other AI platforms, provide standardized Soul export formats, integrate with mainstream Agent frameworks.
Summary
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.
The essence of Soul is a personality configuration layer independent of business logic. 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.
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.
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.
References
- HagiCode Official Site: hagicode.com
- Soul Platform: soul.hagicode.com
- HagiCode GitHub: github.com/HagiCode-org/site
- HagiCode Desktop: hagicode.com/desktop/
- HagiCode Installation Docs: docs.hagicode.com/installation/docker-compose
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.
原文与版权说明
感谢您的阅读,如果您觉得本文有用,欢迎点赞、收藏和分享支持。
本内容采用人工智能辅助协作,最终内容由作者审核并确认。
- 本文作者: newbe36524
- 原文链接: https://docs.hagicode.com/go?platform=devto&target=%2Fblog%2F2026-03-25-hagicode-soul-platform-technical-analysis%2F
- 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处! <!-- hagicode-copyright:end -->
Top comments (0)