In the previous chapter, Global Application State, we built our "Whiteboard"โa central place to store data. But data isn't static. Users come and go, conversations start and stop, and sometimes we need to switch between different tasks.
This is where Session Lifecycle Management comes in.
If Global State is the whiteboard, Session Management is the concept of a "Meeting." A meeting has a specific start time, a specific topic, and a specific ID. When the meeting ends, we wipe the whiteboard. If we resume the meeting later, we need to put everything back exactly how it was.
Imagine you are using a web browser.
If the browser didn't distinguish between Tab A and Tab B, your email text might accidentally appear in the video search bar!
In our agent, a Session groups together:
We need a system to track "Who is talking right now?" and "Where are they?"
There are three main pillars to managing a session in bootstrap.
sessionId)
Every time you start the agent, it generates a unique "Passport Number" called a UUID (Universally Unique Identifier). This string (e.g., a1b2-c3d4...) tags every log line, cost entry, and error message.
projectRoot)This is a subtle but critical concept.
cd src, your CWD changes.
Why the difference?
If the agent is working in /my-app, and it runs cd /my-app/src/utils, it shouldn't forget that it is still working on /my-app. The projectRoot acts as the anchor for the session's history and skills.
switchSession)Sometimes, a user wants to "resume" an old conversation. To do this, we perform a "context switch." We load the old ID and the old directory, effectively teleporting the agent back to the past state.
Let's look at how we interact with the session logic using the functions exported from state.ts.
Any tool that needs to log data needs to know the current Session ID.
import { getSessionId } from './state.js'
function logError(message: string) {
const currentId = getSessionId()
console.log(`[Session: ${currentId}] Error: ${message}`)
}
Sometimes, things go wrong, or the user wants a fresh start without restarting the entire program. We can regenerate the ID.
import { regenerateSessionId } from './state.js'
// Wipe the slate clean
const newId = regenerateSessionId()
console.log("New session started:", newId)
If we need to jump to a specific session (for example, when the user provides a --resume flag), we use switchSession.
import { switchSession } from './state.js'
// Teleport to an existing session
switchSession('existing-session-id', '/path/to/project')
What happens when we switch sessions? It's not just changing a variable; we have to notify the rest of the system so it can reload history.
Let's look at the implementation in state.ts.
projectRoot Anchor
In the State object (discussed in Global Application State), we hold two path variables. Notice how projectRoot is designed to be stable.
type State = {
// Changes frequently (whenever agent runs `cd`)
cwd: string
// Set ONCE at startup. Never changes mid-session.
// Used for history, skills, and session identity.
projectRoot: string
// ... other fields
}
switchSession LogicThis is the mechanic that allows "teleportation."
// state.ts
// 1. We define a signal (an event emitter)
const sessionSwitched = createSignal<[id: SessionId]>()
export function switchSession(
sessionId: SessionId,
projectDir: string | null = null,
): void {
// 2. Clean up old data (caches)
STATE.planSlugCache.delete(STATE.sessionId)
// 3. Atomically update the ID and the directory
STATE.sessionId = sessionId
STATE.sessionProjectDir = projectDir
// 4. Shout to the world: "We moved!"
sessionSwitched.emit(sessionId)
}
Why is this atomic?
We update sessionId and sessionProjectDir in the same function. This ensures we never have a "Zombie State" where we have the new ID but are looking in the old folder for files.
Other parts of the application (like the transcript logger) need to know when the session changes so they can stop writing to the old file and start writing to the new one.
// state.ts exports this subscriber
export const onSessionSwitch = sessionSwitched.subscribe
A listener in another file might look like this:
// concurrentSessions.ts
onSessionSwitch((newSessionId) => {
// Update the lock file to say: "This process now owns newSessionId"
updatePidFile(newSessionId)
})
Session management is the heartbeat of the application. It pumps the "Identity" to all other organs:
sessionId to separate these bills. See Resource & Cost Accounting.sessionId is the primary key used to trace what happened. See Telemetry Infrastructure.In this chapter, we learned:
cwd.switchSession allows the agent to "teleport" context, ensuring history and file paths stay in sync.Now that we know who we are (Session ID) and where we are (Global State), we need to figure out what we are doing. Are we planning? Are we coding? Are we waiting for user input?
Next Chapter: Agent Context & Mode Tracking
Generated by Code IQ