In the previous Chapter 4: General Pacing and Configuration, we built a "Polite Waiter" that opens the survey based on time and message counts.
However, sometimes we want to ask for feedback not because enough time has passed, but because something specific and technical just happened.
Think of your car's dashboard:
In FeedbackSurvey, we have two "Check Engine" lights:
These triggers ignore the general timer. They want to know: "Did that specific technical action work correctly?"
useMemorySurvey)The Goal: If the Assistant pulls up a memory from 3 weeks ago, we want to ask the user, "Did remembering that context help?"
Unlike the General Pacing, which just counts messages, this trigger looks inside the messages. It scans the conversation history for a specific tool use.
We need a helper function to look for the "fingerprint" of a memory read:
// Helper to scan messages
function hasMemoryFileRead(messages): boolean {
for (const message of messages) {
// 1. Check if the Assistant used a tool
if (message.content.type === 'tool_use') {
// 2. Was it the File Read tool?
// 3. Was the file a "memory" file?
if (isMemoryFile(message.input.file_path)) {
return true;
}
}
}
return false;
}
The hook useMemorySurvey runs every time the AI finishes a turn. It acts as a gatekeeper.
It uses a series of simple if statements to filter out false alarms before opening the survey.
// Inside useMemorySurvey.tsx
// 1. Only run if we actually found a memory read
if (!hasMemoryFileRead(messages)) {
return;
}
// 2. Check if the Assistant actually mentioned the word "memory"
// (This prevents confusion if it read the file silently)
if (!text.includes("memory")) {
return;
}
// 3. Roll the dice (20% chance) so we don't annoy the user
if (Math.random() < 0.2) {
open(); // Triggers the State Machine from Chapter 3
}
This ensures we only interrupt the user when the memory feature was clearly used and acknowledged.
usePostCompactSurvey)The Goal: When a conversation gets very long, our system "compacts" (summarizes) the older messages to save space. We want to know: "Did the AI lose the thread of the conversation after summarizing?"
This trigger is tricky. We don't want to ask during the compaction. We want to ask after the next message.
Why? Imagine someone cleans your room (Compaction). You don't know if they lost your keys until you try to find them (Next Message).
We identify the exact message where compaction happened (the "Boundary").
// Inside usePostCompactSurvey.tsx
// 1. Find the message that marks the compaction point
const boundaryIndex = messages.findIndex(
msg => msg.uuid === boundaryUuid
);
// 2. Check if there are NEW messages after that point
if (boundaryIndex === -1) return false;
// If there are messages AFTER the boundary, returns true
return messages.length > boundaryIndex + 1;
We use a React Effect to watch the message list. If we see that we crossed a boundary, we roll the dice.
// Inside the Effect loop
// If we are tracking a boundary...
if (pendingBoundaryUuid) {
// ...and the user has sent a new message since then
if (hasMessageAfterBoundary(messages, pendingBoundaryUuid)) {
// Clear the tracker
pendingBoundaryUuid = null;
// 20% chance to open survey
if (Math.random() < 0.2) {
open();
}
}
}
Let's look at how the Memory Survey decides to open. This happens entirely in the background while the user is reading the AI's response.
Notice that once open() is called, the Survey Lifecycle State Machine takes over completely. The Event Trigger doesn't care about handling clicks or "Thank You" messages. It just pushes the button to start the process.
Both useMemorySurvey and usePostCompactSurvey utilize the exact same state machine we built in Chapter 3.
// Both files use this:
const { state, open, handleSelect } = useSurveyState({
// Custom tracking event for analytics
onOpen: (id) => logEvent('specific_event_triggered', { id }),
// Reuse standard behavior
hideThanksAfterMs: 3000,
});
This demonstrates the power of the architecture:
You have now learned how to trigger surveys based on technical events!
We have covered how to show the survey, how it behaves, and when it opens. But there is one final piece of the puzzle. When the user says "Bad" and agrees to share their transcript, where does that data actually go?
Next Chapter: Transcript Data Submission
Generated by Code IQ