@@ -79,12 +79,16 @@ export interface SessionHeader {
7979 timestamp : string ;
8080 cwd : string ;
8181 parentSession ?: string ;
82+ /** RLM spawn depth. Optional for backward compatibility. Forks preserve the source depth. */
83+ rlmDepth ?: number ;
8284 git ?: GitContext ;
8385}
8486
8587export interface NewSessionOptions {
8688 id ?: string ;
8789 parentSession ?: string ;
90+ /** Explicit RLM spawn depth. An explicitly undefined value suppresses parent derivation. */
91+ rlmDepth ?: number ;
8892}
8993
9094export type SessionPersistListener = ( sessionFile : string ) => void ;
@@ -291,6 +295,8 @@ export interface SessionInfo {
291295 state ?: SessionState ;
292296 /** Path to the parent session (if this session was forked). */
293297 parentSessionPath ?: string ;
298+ /** RLM spawn depth; absent when legacy metadata cannot establish it. */
299+ rlmDepth ?: number ;
294300 created : Date ;
295301 modified : Date ;
296302 messageCount : number ;
@@ -692,6 +698,26 @@ function readSessionHeader(filePath: string): Partial<SessionHeader> | undefined
692698 return JSON . parse ( firstLine ) as Partial < SessionHeader > ;
693699}
694700
701+ function isValidRlmDepth ( value : unknown ) : value is number {
702+ return typeof value === "number" && Number . isSafeInteger ( value ) && value >= 0 ;
703+ }
704+
705+ function deriveChildRlmDepth ( parentHeader : Partial < SessionHeader > | undefined ) : number | undefined {
706+ return isValidRlmDepth ( parentHeader ?. rlmDepth ) ? parentHeader . rlmDepth + 1 : undefined ;
707+ }
708+
709+ function rootRlmDepthFromEnv ( ) : number {
710+ const value = process . env . RLM_DEPTH ;
711+ if ( value === undefined || value === "" ) {
712+ return 0 ;
713+ }
714+ const parsed = Number . parseInt ( value , 10 ) ;
715+ if ( ! Number . isFinite ( parsed ) || parsed < 0 ) {
716+ throw new Error ( "RLM_DEPTH must be a non-negative integer" ) ;
717+ }
718+ return parsed ;
719+ }
720+
695721function isValidSessionFile ( filePath : string ) : boolean {
696722 try {
697723 const header = readSessionHeader ( filePath ) ;
@@ -1021,6 +1047,7 @@ async function scanSessionInfo(filePath: string, stats: Awaited<ReturnType<typeo
10211047 if ( ! header ) return null ;
10221048 const cwd = typeof header . cwd === "string" ? header . cwd : "" ;
10231049 const parentSessionPath = header . parentSession ;
1050+ const rlmDepth = isValidRlmDepth ( header . rlmDepth ) ? header . rlmDepth : parentSessionPath ? undefined : 0 ;
10241051 const modified = getSessionModifiedDateFromLastActivity ( lastActivityTime , header , stats . mtime ) ;
10251052
10261053 return {
@@ -1030,6 +1057,7 @@ async function scanSessionInfo(filePath: string, stats: Awaited<ReturnType<typeo
10301057 name,
10311058 state,
10321059 parentSessionPath,
1060+ rlmDepth,
10331061 created : new Date ( header . timestamp ) ,
10341062 modified,
10351063 messageCount,
@@ -1177,6 +1205,15 @@ export class SessionManager {
11771205 newSession ( options ?: NewSessionOptions ) : string | undefined {
11781206 let sessionId = options ?. id ?? createSessionId ( ) ;
11791207 let sessionFile : string | undefined ;
1208+ const hasExplicitRlmDepth = options !== undefined && Object . hasOwn ( options , "rlmDepth" ) ;
1209+ let parentHeader : Partial < SessionHeader > | undefined ;
1210+ if ( options ?. parentSession && ! hasExplicitRlmDepth ) {
1211+ try {
1212+ parentHeader = readSessionHeader ( options . parentSession ) ;
1213+ } catch {
1214+ // Legacy-invalid or unavailable parents leave the child depth unknown.
1215+ }
1216+ }
11801217 if ( this . persist ) {
11811218 if ( options ?. id ) {
11821219 sessionFile = getSessionFilePath ( this . getSessionDir ( ) , sessionId ) ;
@@ -1193,13 +1230,19 @@ export class SessionManager {
11931230 this . sessionId = sessionId ;
11941231 const timestamp = new Date ( ) . toISOString ( ) ;
11951232 const git = this . persist ? ( captureGitContext ( this . cwd ) ?? undefined ) : undefined ;
1233+ const rlmDepth = hasExplicitRlmDepth
1234+ ? options ?. rlmDepth
1235+ : options ?. parentSession
1236+ ? deriveChildRlmDepth ( parentHeader )
1237+ : rootRlmDepthFromEnv ( ) ;
11961238 const header : SessionHeader = {
11971239 type : "session" ,
11981240 version : CURRENT_SESSION_VERSION ,
11991241 id : this . sessionId ,
12001242 timestamp,
12011243 cwd : this . cwd ,
12021244 parentSession : options ?. parentSession ,
1245+ rlmDepth,
12031246 git,
12041247 } ;
12051248 this . fileEntries = [ header ] ;
@@ -1305,6 +1348,7 @@ export class SessionManager {
13051348 if ( ! existsSync ( dir ) ) {
13061349 mkdirSync ( dir , { recursive : true } ) ;
13071350 }
1351+ const previousHeader = this . getHeader ( ) ;
13081352 const target = createUniqueSessionFileTarget ( dir ) ;
13091353 this . sessionDir = dir ;
13101354 this . sessionId = target . sessionId ;
@@ -1318,6 +1362,8 @@ export class SessionManager {
13181362 id : this . sessionId ,
13191363 timestamp,
13201364 cwd : this . cwd ,
1365+ parentSession : previousHeader ?. parentSession ,
1366+ rlmDepth : previousHeader ?. rlmDepth ?? ( previousHeader ?. parentSession ? undefined : 0 ) ,
13211367 git,
13221368 } ;
13231369 this . fileEntries = [ header , ...this . getEntries ( ) ] ;
@@ -1941,6 +1987,7 @@ export class SessionManager {
19411987 timestamp,
19421988 cwd : this . cwd ,
19431989 parentSession : this . persist ? previousSessionFile : undefined ,
1990+ rlmDepth : this . getHeader ( ) ?. rlmDepth ,
19441991 git : this . persist ? ( captureGitContext ( this . cwd ) ?? undefined ) : undefined ,
19451992 } ;
19461993
@@ -2134,6 +2181,7 @@ export class SessionManager {
21342181 timestamp,
21352182 cwd : targetCwd ,
21362183 parentSession : sourcePath ,
2184+ rlmDepth : sourceHeader . rlmDepth ,
21372185 git : captureGitContext ( targetCwd ) ?? undefined ,
21382186 } ;
21392187 appendFileSync ( newSessionFile , `${ JSON . stringify ( newHeader ) } \n` ) ;
0 commit comments