Code › tail-villain

From Persona Options to Role Boundaries

How interviewer personas moved from a selection UI into distinct interview and study flows

I implemented personas in Tail Villain.

The planning document already had them written down. Kovill was the default interviewer who pushes with follow-up questions. Socrates asked about first principles. Deep Dive pushed into implementation details. Y-Bot asked for evidence and trade-offs. Angel Senior prioritized coaching over pressure. At first, it seemed enough to let the user choose one on the screen and pass a persona key to the backend.


The interview start page got a persona selector, and the session page got the current interviewer name plus a short briefing panel. The user needed to know which interviewer they were talking to in the current session.

I also added a shared persona definition during that pass. If the frontend and backend keep separate definitions, the screen copy and the actual prompt behavior can drift. Using the same key and description gave both sides one source for the selector and the prompt setup.


Even after passing persona data into the prompt, Socrates sometimes sounded like a generic corporate interviewer. The name was Socrates, but the question sounded like a normal company interview question and moved the answer forward without pressing on the underlying principle.

That was not about whether persona data existed. The generic interview rules were still stronger. When structured question format, safe transition copy, and neutral feedback rules come first, the model can receive a persona and still drift back toward a generic interviewer.

So I adjusted the prompt across opening, follow-up, and completion. The opening needed to decide what to check first. The follow-up needed to decide where to push when the answer was shallow. The completion needed to summarize by the right criteria for that persona.

For Socrates, that meant asking about assumptions and first principles. For Deep Dive, it meant pulling abstract explanations down into implementation details. Y-Bot needed to ask for evidence and decision criteria, while Angel Senior needed to handle the same problem with a less aggressive coaching flow.

Writing “you are Socrates” was not enough. The prompt needed to give the persona a direction and a more specific role.


While I was adjusting the prompt, the model of treating every persona as an interviewer also started to feel wrong. Kovill fits mock interviews. It can pressure the answer, ask again where the answer is weak, and evaluate the session.

Socrates, Deep Dive, Y-Bot, and Angel Senior did not need to be mock interviewers. Socrates could help organize concepts through first-principles questions. Deep Dive could ask for more detail about internal behavior. Y-Bot could review reasoning and assumptions. Angel Senior could help unblock the user with less pressure. Those roles made more sense as study coaches.

So I split the roles. Kovill stayed as the mock interview persona, and the others moved into study mode. The interview start page only showed Kovill, while the other personas became selectable from study mode.

That also split the UI. The user had to know whether they were starting an interview or entering a study conversation. It may still look like choosing a character, but the product flow is different.


Study mode had to become an actual chatbot.

I added a separate study-turn API and a coach chat route. In study mode, the user chooses a coach, presses an explicit start action, enters a separate chat route, and the LLM generates the opening and replies. I did not keep the opening as a fixed frontend template.

The opening still needed constraints. If the prompt copied long topic titles or rubric text directly into the first message, it read like instructions instead of a conversation. I changed both mock interview openings and study openings so internal rubric phrases were not exposed directly, and the first question stayed short and singular.


The interview page had also been inferring progress from messages and evaluation data. Older sessions did not all have the same data shape, and some feedback metadata was incomplete, so even completed sessions could render awkward progress states.

For new sessions, I added explicit session state. Instead of reconstructing checkpoints later from the transcript, the session carries which checkpoint it is on and how many follow-ups have happened. Older sessions are marked as legacy, and the UI avoids pretending it knows exact progress when the data does not support it.

This was separate from persona prompting, but it showed up in the same interview screen. The user needs to trust not only what the interviewer asks, but also where the session says they are. A good prompt does not help much if the UI reports the session state incorrectly.


There was also a session recovery issue.

When a user came back after leaving the app open for a while, the app could behave as if the user had been logged out. The access token had expired, and /users/me needed to restore the session, but the frontend refresh interceptor excluded the whole /users/* namespace, so the recovery path never ran.

I narrowed the exclusions to login, register, and refresh endpoints. After that, /users/me could use refresh-token recovery. It was not a persona feature, but if the session drops while the user is answering, the practice flow breaks before the interview behavior matters.


This work ended up being about separating coach roles and making each one actually run that way. Kovill handles pressure and evaluation in mock interviews. The coach personas help organize concepts or reasoning in study mode.

The hard part was not adding names to a selector. The hard part was that the interviewers sometimes said irrelevant things, gave strange answers, or sounded too similar. Fixing that meant tightening the prompt and separating the flows instead of expecting one persona switch to solve it.