Rehearsal Room

MongoDB AI Hackathon · top 5 of 94 teams

voice · behavioral memory · mongodb atlas

github ↗︎ try it here ↗︎

kamila kuatova, ulzhan khamidolla, and i built rehearsal room in three and a half hours at the mongodb ai hackathon, and it placed in the top 5 of 94 teams. the idea came from something most people have done before a hard conversation: replaying it in your head over and over. we wanted a place where you could actually say it out loud to someone who answers back.

you pick a counterpart, press start, and talk. the browser handles speech recognition, next.js coordinates the session, claude writes the counterpart’s reply, and elevenlabs speaks it.

one turn of the rehearsal loop browser speech recognition → text final transcript next.js session routes load active traits from atlas persona + traits + turn claude writes the counterpart's reply reply text elevenlabs turns the reply into a voice audio browser playback mic stays closed while it plays mic reopens after playback

the first thing a voice loop like this has to get right is not hearing itself. if the microphone is still open while the counterpart’s reply plays through the speakers, speech recognition transcribes the generated voice and the counterpart ends up answering its own lines. instead of trying to filter that audio out afterwards, we made it part of the session’s structure. the session moves through a small set of phases, and the mic is only open in two of them. it closes as soon as a line is sent, stays closed while the reply is generated and played, and only reopens once playback has actually ended.

the mic is open in two phases listening mic on: you speak a line line sent, mic off thinking mic off: waiting for reply audio ready speaking mic off: the reply plays director interrupt directing playback cut, mic on: a note, not a line playback ends

the part that made the project different was the director interrupt. in most roleplay tools, if the other person reacts in a way that doesn’t feel real, all you can do is keep going or start over. we wanted it to feel more like directing an actor. while the counterpart is talking, you can cut it off and say something like “she’d get quieter, not louder.” that sentence isn’t a line in the scene. it’s a note about how the counterpart should behave.

the harder question was where that note should go. the easiest version would have added it to the chat prompt, and it would have worked for a few turns. but a prompt only lasts as long as the conversation, and we wanted a correction to change who the counterpart is. so every counterpart lives in mongodb as a set of behavioral traits, with one active trait per category, like how it responds to conflict.

when a note comes in, claude classifies it into the categories that already exist and rewrites the trait’s claim. the categories are passed in as a fixed list, so the model can’t invent a new one that nothing else knows about. then the backend runs a single transaction: it saves the note as evidence, marks the old trait as superseded, and inserts the new version pointing back at the one it replaced. the session reloads the active traits, and the counterpart retakes the same moment with the corrected behavior.

what a correction changes director interrupt you cut in and speak a note the note classify the note into an existing trait category, e.g. conflict_response one mongodb transaction 1. insert note as evidence 2. mark old trait superseded 3. insert new trait, v + 1 reload active traits old version kept as history retake same moment, new behavior

keeping the old trait instead of overwriting it was a small decision that ended up mattering a lot. every trait has a history: what it used to be, which note changed it, and when. that’s what let the trait panel show a correction happening live, and it means a correction is something you can go back and inspect instead of something that disappears when the tab closes. doing all three writes in one transaction also meant the counterpart could never end up with two active traits in the same category, or with a new trait that had no evidence behind it.

three and a half hours is not a lot of time, and plenty of things we talked about didn’t make it in. the thing i took away from it is that memory turned out to be a data modeling problem before it was a prompting problem. once corrections became versioned documents instead of extra text in a prompt, the rest of the design got a lot simpler.