In the previous chapter, Scoped Tool Permissions, we secured our background agent by giving it a restricted set of tools.
We now have a complete system:
But there is one final architectural challenge. How do we store the variables that track all of this (like the cursor position or the "in progress" flag) without causing bugs when we run tests or restart sessions?
In programming, the easiest way to store state is using Global Variables.
Imagine a shared whiteboard in the hallway of an office.
Current Step: 5 on the board.Current Step: 5, and gets confused because Project B hasn't started yet!This is a major problem for:
Instead of a shared whiteboard, we use Closures.
Imagine that every time a new project starts, we build a brand new, sealed room.
In JavaScript/TypeScript, we create this "Room" using a function called initExtractMemories. All state variables live inside this function.
Scenario: Automated Testing. We want to run 50 tests in a row to ensure the memory system works.
Goal:
cursor = 10. Pass.cursor should automatically be back at 0.
If we used global variables, Test 2 would start at 10 and fail. With closures, Test 2 calls init() and gets a fresh state.
Here is how the data is isolated.
Let's look at extractMemories.ts. We don't declare variables at the top of the file. Instead, we wrap them.
We start by creating a function that will hold our state.
// extractMemories.ts
// 1. This is the "Public Handle". Initially, it does nothing.
let extractor = null
// 2. The Initialization Function
export function initExtractMemories(): void {
// 3. State variables are defined INSIDE this function.
// No other part of the app can touch these directly.
let lastMemoryMessageUuid: string | undefined
let inProgress = false
const inFlightExtractions = new Set<Promise<void>>()
// ... (logic continues inside) ...
}
Why this matters: Every time initExtractMemories() is called, lastMemoryMessageUuid is recreated from scratch.
Inside the same function, we define the logic that uses these variables. Because functions in JavaScript remember the variables present when they were created (a "Closure"), runExtraction can see inProgress.
// Inside initExtractMemories()...
async function runExtraction(context) {
// We can read/write the variables defined above
if (inProgress) {
return // Block overlapping runs
}
inProgress = true
try {
// ... do the work ...
} finally {
inProgress = false
}
}
Finally, at the end of initExtractMemories, we assign our internal function to the global extractor variable. This gives the outside world a "remote control" to the clean room.
// Inside initExtractMemories()...
// Assign the internal function to the module-level variable
extractor = async (context) => {
// When the outside world calls 'extractor',
// it actually runs the internal logic bound to this specific scope.
await runExtraction({ context })
}
} // End of initExtractMemories
One specific state variable is very important: inFlightExtractions.
This is a Set (a list) of all background jobs currently running.
When the application needs to shut down, we can't just kill the power. We might corrupt a file if the agent is half-finished writing.
We use a Drain pattern (exposed via closure) to wait for safety.
// Inside initExtractMemories()...
drainer = async (timeoutMs) => {
// If the Set is empty, we are safe to quit.
if (inFlightExtractions.size === 0) return
// Otherwise, wait for all Promises in the Set to finish
await Promise.all(inFlightExtractions)
}
Analogy: It's like a store closing for the night. You lock the door (stop new customers/messages), but you let the customers already inside (in-flight extractions) finish checking out before you turn off the lights.
Congratulations! You have completed the tutorial for extractMemories.
We have built a sophisticated, background memory system that is:
This architecture allows the AI to develop long-term memory. It remembers your preferences, your project details, and your rules, all without you having to manage a database or manually write notes. It simply "listens" in the background and writes files, just like a helpful assistant should.
Happy coding!
Generated by Code IQ