@@ -14,6 +14,11 @@ public class ContextBuilder
1414 private readonly IToolRegistry _toolRegistry ;
1515 private readonly ClawSharpConfig _config ;
1616
17+ // Personality file names (matching OpenClaw)
18+ private const string SoulFileName = "SOUL.md" ;
19+ private const string UserFileName = "USER.md" ;
20+ private const string IdentityFileName = "IDENTITY.md" ;
21+
1722 public ContextBuilder (
1823 IMemoryStore memoryStore ,
1924 IToolRegistry toolRegistry ,
@@ -24,17 +29,78 @@ public ContextBuilder(
2429 _config = config ;
2530 }
2631
32+ /// <summary>
33+ /// Reads a personality file from the workspace directory.
34+ /// Returns null if the file doesn't exist.
35+ /// </summary>
36+ private async Task < string ? > ReadPersonalityFileAsync ( string fileName , CancellationToken ct = default )
37+ {
38+ var filePath = Path . Combine ( _config . WorkspaceDir , fileName ) ;
39+ if ( ! File . Exists ( filePath ) )
40+ return null ;
41+
42+ try
43+ {
44+ return await File . ReadAllTextAsync ( filePath , ct ) ;
45+ }
46+ catch
47+ {
48+ return null ;
49+ }
50+ }
51+
2752 /// <summary>
2853 /// Builds the full system prompt including identity, memories, and tools.
2954 /// </summary>
3055 public async Task < string > BuildSystemPromptAsync ( CancellationToken ct = default )
3156 {
3257 var sections = new List < string > ( ) ;
3358
34- // Identity section
35- sections . Add ( $ "# Identity\n You are ClawSharp, an AI assistant running on .NET.") ;
59+ // Read personality files from workspace
60+ var soulContent = await ReadPersonalityFileAsync ( SoulFileName , ct ) ;
61+ var userContent = await ReadPersonalityFileAsync ( UserFileName , ct ) ;
62+ var identityContent = await ReadPersonalityFileAsync ( IdentityFileName , ct ) ;
63+
64+ // Identity section - base identity
65+ var baseIdentity = "You are ClawSharp, an AI assistant running on .NET." ;
66+
67+ // If IDENTITY.md exists, use it to customize the identity
68+ if ( ! string . IsNullOrEmpty ( identityContent ) )
69+ {
70+ var identityLines = identityContent . Split ( '\n ' )
71+ . Select ( l => l . Trim ( ) )
72+ . Where ( l => ! string . IsNullOrEmpty ( l ) && ! l . StartsWith ( "#" ) && ! l . StartsWith ( "---" ) && ! l . StartsWith ( "*" ) && ! l . StartsWith ( "Notes:" ) )
73+ . ToList ( ) ;
74+
75+ // Try to extract name from IDENTITY.md
76+ var nameLine = identityLines . FirstOrDefault ( l => l . StartsWith ( "- **Name:**" ) ) ;
77+ if ( nameLine != null )
78+ {
79+ var name = nameLine . Replace ( "- **Name:**" , "" ) . Trim ( ) ;
80+ if ( ! string . IsNullOrEmpty ( name ) )
81+ {
82+ baseIdentity = $ "You are { name } , { baseIdentity } ";
83+ }
84+ }
85+ }
86+
87+ sections . Add ( $ "# Identity\n { baseIdentity } ") ;
3688 sections . Add ( $ "Current time: { DateTime . UtcNow : yyyy-MM-dd HH:mm:ss} UTC") ;
3789
90+ // SOUL.md - personality and tone guidance
91+ if ( ! string . IsNullOrEmpty ( soulContent ) )
92+ {
93+ sections . Add ( $ "\n # SOUL.md - Your Persona") ;
94+ sections . Add ( soulContent ) ;
95+ }
96+
97+ // USER.md - about the user
98+ if ( ! string . IsNullOrEmpty ( userContent ) )
99+ {
100+ sections . Add ( $ "\n # USER.md - About the User") ;
101+ sections . Add ( userContent ) ;
102+ }
103+
38104 // Memories section
39105 var memories = await _memoryStore . ListAsync ( MemoryCategory . Core , limit : 20 , ct : ct ) ;
40106 if ( memories . Count > 0 )
@@ -58,6 +124,13 @@ public async Task<string> BuildSystemPromptAsync(CancellationToken ct = default)
58124 }
59125 }
60126
127+ // Add guidance about SOUL.md if present
128+ if ( ! string . IsNullOrEmpty ( soulContent ) )
129+ {
130+ sections . Add ( "\n ## Persona Note" ) ;
131+ sections . Add ( "If SOUL.md is present, embody its persona and tone. Avoid stiff, generic replies; follow its guidance unless higher-priority instructions override it." ) ;
132+ }
133+
61134 return string . Join ( "\n \n " , sections ) ;
62135 }
63136
0 commit comments