@@ -5,6 +5,9 @@ internal class MemoryManager
55 {
66 private const int DailyRetentionDays = 30 ;
77 private const int MaxItemsPerList = 80 ;
8+ private const int MaxContextChars = 12000 ;
9+ private const int MaxContextLineChars = 280 ;
10+ private const int MaxAgentInstructionsChars = 3500 ;
811 private readonly string _rootDir ;
912 private readonly string _profilePath ;
1013 private readonly string _dailyDir ;
@@ -249,66 +252,73 @@ public bool MaybeSaveHeartbeat(string userInput, int roundNumber)
249252
250253 var projectKey = ResolveProjectKey ( null ) ! ;
251254 var projectMemories = LoadProjectMemories ( projectKey ) ;
252- var projectAgents = LoadProjectAgents ( projectKey ) ;
255+ var workspaceAgents = LoadWorkspaceAgents ( ) ;
256+ var projectAgents = string . IsNullOrWhiteSpace ( workspaceAgents )
257+ ? LoadProjectAgents ( projectKey )
258+ : string . Empty ;
253259
254260 if ( profile . Count == 0 && dailyList . Count == 0 && relatedNotes . Count == 0 && relatedDaily . Count == 0
255- && projectMemories . Count == 0 && string . IsNullOrWhiteSpace ( projectAgents ) )
261+ && projectMemories . Count == 0 && string . IsNullOrWhiteSpace ( workspaceAgents ) && string . IsNullOrWhiteSpace ( projectAgents ) )
256262 {
257263 return null ;
258264 }
259265
260266 var sb = new StringBuilder ( ) ;
267+ var remainingChars = MaxContextChars - ( "<memory>\n </memory>\n " . Length ) ;
261268 sb . AppendLine ( "<memory>" ) ;
262269
263270 if ( profile . Count > 0 )
264271 {
265- sb . AppendLine ( "profile:" ) ;
266- foreach ( var item in profile . OrderByDescending ( x => x . UpdatedAt ?? x . CreatedAt ) . Take ( maxProfile ) )
267- {
268- sb . AppendLine ( $ "- { item . Content } " ) ;
269- }
272+ AppendSection (
273+ sb ,
274+ "profile:" ,
275+ profile . OrderByDescending ( x => x . UpdatedAt ?? x . CreatedAt ) . Take ( maxProfile ) . Select ( x => x . Content ) ,
276+ ref remainingChars ) ;
270277 }
271278
272279 if ( projectMemories . Count > 0 )
273280 {
274- sb . AppendLine ( $ "project ( { projectKey } ):" ) ;
275- foreach ( var item in projectMemories . OrderByDescending ( x => x . UpdatedAt ?? x . CreatedAt ) . Take ( 20 ) )
276- {
277- sb . AppendLine ( $ "- { item . Content } " ) ;
278- }
281+ AppendSection (
282+ sb ,
283+ $ "project ( { projectKey } ):" ,
284+ projectMemories . OrderByDescending ( x => x . UpdatedAt ?? x . CreatedAt ) . Take ( 20 ) . Select ( x => x . Content ) ,
285+ ref remainingChars ) ;
279286 }
280287
281- if ( ! string . IsNullOrWhiteSpace ( projectAgents ) )
288+ if ( ! string . IsNullOrWhiteSpace ( workspaceAgents ) )
282289 {
283- sb . AppendLine ( $ "project-agents ({ projectKey } /AGENTS.md):") ;
284- sb . AppendLine ( projectAgents ) ;
290+ AppendBlock ( sb , "workspace-agents (AGENTS.md):" , workspaceAgents , MaxAgentInstructionsChars , ref remainingChars ) ;
291+ }
292+ else if ( ! string . IsNullOrWhiteSpace ( projectAgents ) )
293+ {
294+ AppendBlock ( sb , $ "project-agents (.ycode/projects/{ projectKey } /AGENTS.md):", projectAgents , MaxAgentInstructionsChars , ref remainingChars ) ;
285295 }
286296
287297 if ( dailyList . Count > 0 )
288298 {
289- sb . AppendLine ( $ "daily ( { todayKey } ):" ) ;
290- foreach ( var item in dailyList . OrderByDescending ( x => x . UpdatedAt ?? x . CreatedAt ) . Take ( 30 ) )
291- {
292- sb . AppendLine ( $ "- { item . Content } " ) ;
293- }
299+ AppendSection (
300+ sb ,
301+ $ "daily ( { todayKey } ):" ,
302+ dailyList . OrderByDescending ( x => x . UpdatedAt ?? x . CreatedAt ) . Take ( 30 ) . Select ( x => x . Content ) ,
303+ ref remainingChars ) ;
294304 }
295305
296306 if ( relatedDaily . Count > 0 )
297307 {
298- sb . AppendLine ( "daily-related:" ) ;
299- foreach ( var item in relatedDaily )
300- {
301- sb . AppendLine ( $ "- [ { item . DateKey } ] { item . Content } ") ;
302- }
308+ AppendSection (
309+ sb ,
310+ "daily-related:" ,
311+ relatedDaily . Select ( x => $ "[ { x . DateKey } ] { x . Content } ") ,
312+ ref remainingChars ) ;
303313 }
304314
305315 if ( relatedNotes . Count > 0 )
306316 {
307- sb . AppendLine ( "notes:" ) ;
308- foreach ( var note in relatedNotes )
309- {
310- sb . AppendLine ( $ "- { note . Title } : { note . Preview } ") ;
311- }
317+ AppendSection (
318+ sb ,
319+ "notes:" ,
320+ relatedNotes . Select ( x => $ " { x . Title } : { x . Preview } ") ,
321+ ref remainingChars ) ;
312322 }
313323
314324 sb . AppendLine ( "</memory>" ) ;
@@ -338,6 +348,14 @@ private string LoadProjectAgents(string projectKey)
338348 return string . IsNullOrWhiteSpace ( content ) ? string . Empty : content ;
339349 }
340350
351+ private string LoadWorkspaceAgents ( )
352+ {
353+ var path = Path . Combine ( _workDir , "AGENTS.md" ) ;
354+ if ( ! File . Exists ( path ) ) return string . Empty ;
355+ var content = File . ReadAllText ( path ) . Trim ( ) ;
356+ return string . IsNullOrWhiteSpace ( content ) ? string . Empty : content ;
357+ }
358+
341359 private List < MemoryItem > LoadProjectMemories ( string projectKey )
342360 {
343361 var path = Path . Combine ( _projectsDir , projectKey , "memory.json" ) ;
@@ -552,6 +570,79 @@ private static string CompactText(string text, int maxChars)
552570 return compact . Length <= maxChars ? compact : compact [ ..maxChars ] + "..." ;
553571 }
554572
573+ private static string TrimMultiline ( string text , int maxChars )
574+ {
575+ var normalized = text . Replace ( "\r \n " , "\n " ) . Trim ( ) ;
576+ if ( normalized . Length <= maxChars )
577+ {
578+ return normalized ;
579+ }
580+
581+ return normalized [ ..Math . Max ( 0 , maxChars - 3 ) ] . TrimEnd ( ) + "..." ;
582+ }
583+
584+ private static void AppendSection ( StringBuilder sb , string header , IEnumerable < string > items , ref int remainingChars )
585+ {
586+ var materialized = items
587+ . Select ( x => CompactText ( x , MaxContextLineChars ) )
588+ . Where ( x => ! string . IsNullOrWhiteSpace ( x ) )
589+ . ToList ( ) ;
590+
591+ if ( materialized . Count == 0 || ! TryAppendLine ( sb , header , ref remainingChars ) )
592+ {
593+ return ;
594+ }
595+
596+ foreach ( var item in materialized )
597+ {
598+ if ( ! TryAppendLine ( sb , $ "- { item } ", ref remainingChars ) )
599+ {
600+ break ;
601+ }
602+ }
603+ }
604+
605+ private static void AppendBlock ( StringBuilder sb , string header , string content , int maxChars , ref int remainingChars )
606+ {
607+ if ( string . IsNullOrWhiteSpace ( content ) || ! TryAppendLine ( sb , header , ref remainingChars ) )
608+ {
609+ return ;
610+ }
611+
612+ foreach ( var line in TrimMultiline ( content , maxChars ) . Split ( '\n ' ) )
613+ {
614+ if ( ! TryAppendLine ( sb , line , ref remainingChars ) )
615+ {
616+ break ;
617+ }
618+ }
619+ }
620+
621+ private static bool TryAppendLine ( StringBuilder sb , string line , ref int remainingChars )
622+ {
623+ if ( remainingChars <= 0 )
624+ {
625+ return false ;
626+ }
627+
628+ var normalized = line . Replace ( "\r " , string . Empty ) ;
629+ var required = normalized . Length + Environment . NewLine . Length ;
630+ if ( required > remainingChars )
631+ {
632+ if ( remainingChars <= 3 + Environment . NewLine . Length )
633+ {
634+ return false ;
635+ }
636+
637+ normalized = normalized [ ..Math . Max ( 0 , remainingChars - Environment . NewLine . Length - 3 ) ] . TrimEnd ( ) + "..." ;
638+ required = normalized . Length + Environment . NewLine . Length ;
639+ }
640+
641+ sb . AppendLine ( normalized ) ;
642+ remainingChars -= required ;
643+ return true ;
644+ }
645+
555646 private static string ? ResolveDateKey ( string ? date )
556647 {
557648 if ( string . IsNullOrWhiteSpace ( date ) ) return DateTime . Now . ToString ( "yyyy-MM-dd" ) ;
@@ -590,7 +681,3 @@ internal class MemoryItem
590681 }
591682}
592683
593-
594-
595-
596-
0 commit comments