writing · oct 2026 · 6 min read

A mental-health chatbot that knows when not to generate

Designing MindNet so crisis messages never reach the text generator, and why “100% of flagged crises routed” is not the same as “every crisis caught”.

  • Safety
  • NLP
  • MindNet

MindNet is a research project, not a clinical tool. If you or someone you know is in crisis, contact local emergency services, or call or text 988 in the US.

Most chatbot work is about making the model say the right thing. In a mental-health chatbot, the most important decision runs the other way: in the moments that matter most, the model shouldn't try to say anything clever at all.

MindNet is a conversational model for mental-health support that I built in May 2025. It was trained from scratch, runs locally, and is designed around one rule: crisis messages never reach the text generator.

  • 25,000+labeled training interactions
  • 88%classification accuracy
  • 0.84macro-F1 across intents and emotions
  • 100%of flagged crises routed to support

Why no LLM API

The obvious approach in 2025 was to call a large language model. I deliberately didn't, for three reasons:

  • Privacy. Mental-health conversations are some of the most sensitive data there is. MindNet runs locally, so no conversation is sent to a third-party AI service.
  • Predictability. A model you train yourself, with a small set of outputs, is far easier to test than a general-purpose generator.
  • Softening. Generative models tend to soften or hedge. In a crisis, the response should be clear and consistent every single time.

The architecture

A shared bidirectional LSTM encoder over frozen Word2Vec embeddings feeds three classifiers at once:

  • Crisis triage: is this message a crisis?
  • Mood scoring: 15 mood classes plus a continuous score for how positive or negative the message is.
  • Cognitive-distortion tagging: patterns from CBT, such as catastrophizing.

A sequence-to-sequence generator with attention writes the reply, conditioned on what the classifiers found so the tone fits the conversation. Across intent and emotion categories the model reached 88% classification accuracy and a 0.84 macro-F1 score.

MindNet's crisis bypass A message goes through a shared Word2Vec and BiLSTM encoder into three classifiers: crisis triage, mood scoring and distortion tagging. If crisis triage flags the message, a fixed, pre-reviewed support response is returned and the generator never runs. Otherwise a Seq2Seq generator writes a reply using the mood and distortion signals. Message EncoderWord2Vec + BiLSTM Crisis triage Mood scoring Distortion tags Fixed responsesupport resources Seq2Seq generatortone-matched reply flagged not flagged
If crisis triage flags a message, the generator never runs.

The bypass

The most important design decision is a single branch: if the crisis classifier flags a message, the generator never runs. Instead, MindNet returns a fixed, pre-written response with support resources. 100% of flagged crisis messages take that path.

Simplified sketch of the pattern (not production code)

def respond(message: str) -> str:
    signals = classify(message)              # crisis, mood, distortions
    if signals.crisis:
        return CRISIS_RESPONSE               # fixed text, reviewed in advance
    return generate(message, mood=signals.mood, distortions=signals.distortions)

That makes the high-risk path boring on purpose. There's no sampling and no chance of a creatively worded reply. It's the same safe answer every time, and it can be reviewed in advance by people who know what a good crisis response looks like.

The honest caveat

"100% of flagged crises are routed to support" is true, and it's a guarantee about the routing. It isn't a claim that every crisis gets flagged. The bypass is only as good as the classifier in front of it.

That changes what you optimize. For crisis triage, a false alarm costs little: someone gets support resources they didn't strictly need. A miss is far worse. So for this classifier, recall on the crisis class matters more than overall accuracy, and it's the number I'd watch first when improving the system.

When a system has a high-stakes path, make that path deterministic, and then be precise about what your metrics actually guarantee.

What I'd tell someone building one

  • Decide what must never be generated before you decide how to generate everything else.
  • Make the safety path a hard branch in code, not an instruction in a prompt.
  • Report the right metric for the right risk: average accuracy hides the one class that matters most.
  • Keep sensitive data local when you can. It removes a whole category of risk.