In the previous chapter, Chapter 4: Context Assembly, we learned how to pack all the necessary data (the "dossier") for our AI model. We gathered the system prompt, user context, and conversation history.
Now, we face the final challenge: Managing the Aftermath.
Think of the compaction process like moving houses.
If you only do the "Action" (moving the boxes) but forget the "Lifecycle" (reconnecting the internet), you will sit in your new house in the dark.
In software, Lifecycle Hooks are these setup and teardown tasks. They ensure the application is prepared before the heavy work starts, and properly cleaned up afterwards.
The user runs /compact.
We need a system to run cleanup tasks automatically.
Before we start the heavy compaction process, other parts of the application (or external plugins) might want to say something.
For example, a "Coding Assistant Plugin" might want to inject a rule: "If you summarize code, don't just describe it; keep the function signatures."
We run these hooks in parallel with our context assembly to save time.
// Inside compactViaReactive()
const [hookResult, cacheSafeParams] = await Promise.all([
// 1. Run external hooks (Setup)
executePreCompactHooks(
{ trigger: 'manual', customInstructions },
context.abortController.signal,
),
// 2. Build context (from Chapter 4)
getCacheSharingParams(context, messages),
]);
Explanation:
Promise.all: We do two things at once. We ask plugins for their input (executePreCompactHooks) while we gather system data (getCacheSharingParams).hookResult: This contains any extra instructions plugins want to add.Now we have two sets of instructions:
We need to combine them so the AI obeys both.
const mergedInstructions = mergeHookInstructions(
customInstructions, // User's input
hookResult.newCustomInstructions, // Plugin's input
);
Explanation:
mergedInstructions: The final string sent to the AI (e.g., "Make it funny. Keep function signatures.").Once the compaction is finished, the "world" has changed. Old messages are gone. We need to reset the state of the application to match this new reality.
We perform a series of cleanup tasks immediately after success.
// 1. Reset the "bookmark" for the last summary
setLastSummarizedMessageId(undefined);
// 2. Clear the cache so we don't use old, deleted data
getUserContext.cache.clear?.();
// 3. Hide the "Memory Full" warning banner
suppressCompactWarning();
// 4. Run general cleanup scripts
runPostCompactCleanup();
Explanation:
setLastSummarizedMessageId: Since we deleted the old messages, any "bookmarks" pointing to them are now invalid. We remove them.suppressCompactWarning: This solves our main use case! It forces the UI to hide the "Memory Full" warning because we just fixed it.What if the compaction fails? What if the internet cuts out?
When we started, we likely showed a "Loading..." spinner. If the code crashes, that spinner might spin forever, freezing the app.
We use a try/finally block to ensure the UI is always reset, no matter what happens.
try {
// ... Attempt the heavy compaction work ...
} finally {
// This code runs on Success OR Failure
context.onCompactProgress?.({ type: 'compact_end' });
// Unlock the input box so the user can type again
context.setSDKStatus?.(null);
}
Explanation:
finally: This is the ultimate cleanup hook. It guarantees that the application never gets stuck in a "Loading" state.
Let's visualize the entire timeline of the /compact command, seeing how hooks wrap around the main logic.
In compact.ts, you can see these concepts integrated directly into the orchestration logic.
The cleanup isn't just about UI; it's about data integrity. In the compactViaReactive function, we combine the display message from the hooks with the result from the engine.
// Combine messages from the Plugin (Hook) and the Engine
const combinedMessage =
[hookResult.userDisplayMessage, outcome.result.userDisplayMessage]
.filter(Boolean)
.join('\n') || undefined;
return {
type: 'compact',
// ... return the combined result
displayText: buildDisplayText(context, combinedMessage),
}
Why do this?
If a plugin did some work (like archiving data to a file) during the PreCompact phase, it might want to tell the user "Archived 3 files." The engine wants to say "Summarized chat." We join these strings so the user sees:
"Archived 3 files. Summarized chat."
Congratulations! You have completed the Compact Project Tutorial.
We have traveled a long way:
You now understand the architecture of a production-grade CLI command. It isn't just about running a function; it's about managing resources, handling errors gracefully, and keeping the user interface in sync with the application state.
Generated by Code IQ