Welcome to the final chapter of the SessionMemory project tutorial!
In the previous chapter, Prompt Construction & Templating, we taught our AI how to format its notes using strict templates. We now have a system that can read, decide when to update, spawn a worker, and write structured summaries.
However, there is one final, invisible danger lurking in our system: Speed.
What happens if the user sends two messages very quickly? Or what if a background process tries to update the memory at the exact same moment the main agent is trying to read it?
If two processes try to edit session-memory.md at the same time, they might overwrite each other's work. We need a "Traffic Cop" to manage the flow. In this chapter, we will build Shared State & Concurrency Control.
Imagine a busy intersection:
We need a system that does this:
Usually, variables live inside specific functions and die when the function ends. Shared State refers to variables that live outside functions, sitting in a central module. They act as a "Dashboard" that any part of the program can look at to see the current status of the system.
Concurrency means "things happening at the same time." Control means "preventing chaos." We use a Lock mechanism. When one process starts working, it "locks" the dashboard. Anyone else who checks the dashboard sees the lock and knows they must wait.
In programming, if File A imports File B, and File B imports File A, the computer gets confused (a "circular dependency").
sessionMemory.ts (Main Logic) needs runAgent.sessionMemory.ts, other files might get stuck in a loop trying to access them.sessionMemoryUtils.ts. Everyone can talk to the neutral third party without creating a circle.Here is how our Traffic Cop manages the intersection.
If another process tries to run while the State is LOCKED, it sees the lock and pauses.
The implementation lives in sessionMemoryUtils.ts. This file acts as the "Central Truth" for our memory system.
These variables sit at the top of the file, outside of any function. They hold the state for the entire application lifetime.
// Inside sessionMemoryUtils.ts
// 1. The Configuration (Thresholds, limits)
let sessionMemoryConfig = { ...DEFAULT_SESSION_MEMORY_CONFIG }
// 2. The Lock (Timestamp of when work started)
let extractionStartedAt: number | undefined
// 3. The Last Checkpoint (How big was the file last time?)
let tokensAtLastExtraction = 0
extractionStartedAt is our lock. If it is undefined, the door is open. If it is a number (a timestamp), the door is locked.We need simple functions to turn the lock on and off.
/**
* Turn the Lock ON
*/
export function markExtractionStarted(): void {
extractionStartedAt = Date.now()
}
/**
* Turn the Lock OFF
*/
export function markExtractionCompleted(): void {
extractionStartedAt = undefined
}
markExtractionStarted. When the worker finishes, it calls markExtractionCompleted.This is the most important function. If the lock is on, we must make the program wait.
export async function waitForSessionMemoryExtraction(): Promise<void> {
const startTime = Date.now()
// Loop while the lock is active
while (extractionStartedAt) {
// Check for "Stale" locks (Broken process?)
const timeElapsed = Date.now() - extractionStartedAt
if (timeElapsed > 60000) return // If > 1 min, ignore lock
// Wait 1 second before checking again
await sleep(1000)
}
}
1. The Loop: As long as extractionStartedAt has a value, we stay in this loop.
2. Safety Valve: What if the worker crashes and never unlocks the door? We check if the lock is older than 60 seconds. If so, we assume the worker died and we proceed anyway.
3. Sleep: We pause for 1 second (sleep(1000)) to avoid overloading the CPU.
In Chapter 3: Update Threshold Logic, we talked about checking if the conversation grew by 5,000 tokens. To do that, we need to remember the previous size.
/**
* Save the size of the conversation after an update
*/
export function recordExtractionTokenCount(currentTokenCount: number): void {
tokensAtLastExtraction = currentTokenCount
}
/**
* Calculate growth
*/
export function hasMetUpdateThreshold(currentTokenCount: number): boolean {
const growth = currentTokenCount - tokensAtLastExtraction
return growth >= sessionMemoryConfig.minimumTokensBetweenUpdate
}
tokensAtLastExtraction is the benchmark. We update it here in the shared state so the logic in Chapter 3 can access it cleanly.Why did we add that 60-second check in Step 3?
Imagine this scenario:
If we stored the lock in a permanent database, the system would wake up, see the lock, and wait forever.
Since our variables are in-memory (RAM), restarting the application resets them. However, if just the worker crashes but the main application keeps running, the variable extractionStartedAt would stay set forever. The 60-second timeout ensures that a crashed worker doesn't freeze the memory system permanently.
Congratulations! You have completed the SessionMemory tutorial series.
Let's recap the journey:
You now have a fully functional, self-updating, persistent memory system for an AI Agent. The AI can now remember context over long periods, survive restarts, and keep its notes organizedβall automatically!
Thank you for following along with the SessionMemory project. Happy coding!
Generated by Code IQ