DEV Community

473185670
473185670

Posted on

How I Built a Context-Dependent Core Belief Detector in 110 Lines of Vanilla JavaScript - No AI, No NLP, No Attention Mechanism

The same words mean different things in different contexts.

"I'm not good enough" at work means I'm incompetent.
"I'm not good enough" in a relationship means I'm unlovable.
"I'm not good enough" with family means I'm a burden.

A classifier that ignores context will misclassify thoughts - because the same surface text resolves to different core beliefs depending on the life domain where it was activated.

This is the problem that attention mechanisms solve in modern NLP: the same token should contribute differently to the output depending on the surrounding context. But you don't need a transformer to do context-dependent classification. You need a 2D lookup table.

I built a context-dependent core belief detector that takes two inputs - a thought and a life domain - and classifies the thought differently based on the domain. No AI, no NLP, no attention mechanism. Just a domain-keyed map.

Live tool here - try it, then come back for the algorithm.

The Problem: Context-Free Classification

My earlier cognitive distortion detector uses flat classification:

function analyze(thought) {
  // f(thought) ? distortion
  for (const d of DISTORTIONS) {
    if (thought.includes(d.pattern)) return d;
  }
}
Enter fullscreen mode Exit fullscreen mode

This works for distortions because "catastrophizing" means the same thing regardless of context. "This is a disaster" is catastrophizing whether it happened at work or at home.

But core beliefs are context-dependent. "I'm not good enough" doesn't tell you which core belief is activated - you need to know where the thought came up. The same phrase maps to different beliefs in different life domains:

Thought Domain Core Belief
"I'm not good enough" Work I'm incompetent
"I'm not good enough" Relationships I'm unlovable
"I'm not good enough" Family I'm a burden
"I'm not good enough" Appearance I'm ugly

A context-free classifier would pick one belief for "not good enough" and be wrong 75% of the time.

The Solution: Domain-Keyed Classification

Instead of a flat list of patterns, I use a 2D data structure - a map keyed by domain, where each domain has its own set of keyword?belief mappings:

const DOMAINS = {
  work: {
    label: "Work / Achievement",
    beliefs: [
      { belief: "I'm incompetent",
        patterns: ["can't do","not good enough","don't know how",
                   "going to fail","mess up","not capable"],
        reframe: "Competence is built through practice, not innate..." },
      { belief: "I'm a failure",
        patterns: ["failed","failure","fired","rejected","passed over"],
        reframe: "A failure is an event, not an identity..." },
      { belief: "I'm stupid",
        patterns: ["stupid","idiot","dumb","can't understand"],
        reframe: "Intelligence is not a single fixed trait..." },
      { belief: "I'm defective",
        patterns: ["broken","something wrong with me","don't fit"],
        reframe: "Different is not defective..." }
    ]
  },
  social: {
    label: "Relationships / Social",
    beliefs: [
      { belief: "I'm unlovable",
        patterns: ["not good enough","nobody loves","unlovable",
                   "rejected","abandoned"],
        reframe: "Being rejected by one person doesn't mean..." },
      { belief: "I'm boring",
        patterns: ["boring","nothing to say","dull","awkward"],
        reframe: "Quiet is not boring..." },
      { belief: "I'm unimportant",
        patterns: ["don't matter","nobody cares","invisible"],
        reframe: "People show they care differently..." },
      { belief: "I'm defective",
        patterns: ["broken","weird","not normal"],
        reframe: "Social awkwardness is common and learnable..." }
    ]
  },
  // ... 5 more domains: romantic, family, appearance,
  //     competence, social_worth
};
Enter fullscreen mode Exit fullscreen mode

Notice: "not good enough" appears in BOTH domains but maps to different beliefs:

  • In work ? "I'm incompetent"
  • In social ? "I'm unlovable"

This is the key difference from flat classification. The pattern is the same; the label it resolves to depends on which domain's pattern list you search.

The Core Algorithm: f(thought, domain) ? belief

function detectBelief(thought, domainKey) {
  var t = thought.toLowerCase().trim();
  var domain = DOMAINS[domainKey];
  if (!domain) return null;

  var results = [];
  for (var i = 0; i < domain.beliefs.length; i++) {
    var b = domain.beliefs[i];
    var matchedPatterns = [];
    for (var j = 0; j < b.patterns.length; j++) {
      if (t.indexOf(b.patterns[j]) > -1) {
        matchedPatterns.push(b.patterns[j]);
      }
    }
    if (matchedPatterns.length > 0) {
      var confidence = matchedPatterns.length >= 3 ? "high"
                      : matchedPatterns.length === 2 ? "medium"
                      : "low";
      results.push({
        belief: b.belief,
        patterns: matchedPatterns,
        confidence: confidence,
        reframe: b.reframe,
        domain: domain.label
      });
    }
  }

  // Sort by confidence: high > medium > low
  var order = { high: 0, medium: 1, low: 2 };
  results.sort(function(a, b) {
    return order[a.confidence] - order[b.confidence];
  });
  return results;
}
Enter fullscreen mode Exit fullscreen mode

The function takes two parameters - thought and domainKey. It looks up the domain in the DOMAINS map, then searches only that domain's beliefs. The same thought produces different results depending on which domain you pass.

This is the entire algorithm. 25 lines. The complexity is in the data structure (the 2D map), not the code.

The Insight Feature: Showing What Context Changes

The most interesting part isn't the detection - it's showing the user what their thought would detect in other domains. This makes the context-dependence visible:

function detectInOtherDomains(thought, currentDomain) {
  var t = thought.toLowerCase().trim();
  var others = [];
  for (var key in DOMAINS) {
    if (key === currentDomain) continue;
    var r = detectBelief(thought, key);
    if (r && r.length > 0) {
      others.push({
        domain: DOMAINS[key].label,
        belief: r[0].belief
      });
    }
  }
  return others;
}
Enter fullscreen mode Exit fullscreen mode

When the user enters "I'm not good enough" in the romantic domain, the tool detects "I'm unlovable" - and then shows:

Context matters: The same thought in other domains would detect: Work ? I'm incompetent, Family ? I'm a burden, Appearance ? I'm ugly. The belief that fits depends on the context.

This is the moment of insight. The user sees that their thought doesn't have one fixed meaning - it activates different core beliefs in different areas of life. The context isn't a footnote; it's the determining factor in which belief gets triggered.

Why This Is Like Attention (But Simpler)

In a transformer, attention works like this:

  1. For each token, compute attention weights over all other tokens
  2. Weight the context tokens by their attention scores
  3. Use the weighted context to modify the token's representation

My approach does something structurally similar:

  1. For each thought, the user selects the relevant domain (instead of computing attention weights)
  2. The domain weights which beliefs are relevant (instead of weighting context tokens)
  3. The domain modifies which patterns the thought is matched against (instead of modifying the token representation)

The difference: attention computes a continuous weighting over all possible contexts. My approach uses a discrete selection of one context. This is simpler, more interpretable, and - for a known, small set of life domains - equally effective.

The user is the attention mechanism. They know which domain they're in. The classifier just needs to use that information.

Why Rule-Based > LLM for This

You could fine-tune an LLM to do context-dependent core belief detection. But for this specific problem, rule-based is better:

1. The output space is small and known. There are 13 core beliefs (from decades of CBT research). This doesn't need a language model's 50,000-token vocabulary. It needs a lookup into a table with 13 rows.

2. The context is discrete, not continuous. Life domains are enumerable categories (work, social, romantic, family, etc.), not continuous embeddings. A dropdown selector captures the context perfectly - no attention matrix needed.

3. Determinism is the feature. The same thought in the same domain should always produce the same belief. An LLM might classify "I'm not good enough" + work as "I'm incompetent" on one run and "I'm a failure" on another. For a CBT technique, nondeterminism is a bug.

4. The reframes are therapist-authored. Each belief has a specific reframe written by someone who understands CBT. An LLM would generate plausible-sounding but potentially generic reframes. The quality of a CBT reframe comes from clinical knowledge, not language fluency.

5. Zero privacy risk. The user's thoughts stay in their browser. No API call, no server, no data leaving the device. For mental health data, this isn't a nice-to-have - it's essential.

6. The context comparison is deterministic. The "what would this thought detect in other domains?" feature works because the classification is rule-based. With an LLM, you'd need 7 API calls (one per domain) to show the comparison - and each might return a different format.

The Architecture Decision

The key architectural choice is: where does the context come from?

Approach Context source Cost Accuracy
Flat classifier (my earlier detector) None O(n) patterns Misclassifies context-dependent thoughts
LLM with prompt Inferred from text API call + latency Good but nondeterministic
LLM with attention Inferred from all tokens GPU inference Good but opaque
Domain-keyed map (this tool) User selects O(n/k) patterns per domain Exact for known domains

The domain-keyed map is the simplest approach that solves the context problem. It works because:

  • The user knows which domain they're in (they don't need the model to infer it)
  • The domains are enumerable (there aren't 10,000 possible contexts)
  • Each domain has a small, stable set of relevant beliefs

This is the rule-based equivalent of hard attention - instead of computing soft attention weights, we use a hard selection. For a small, known context space, hard attention is sufficient and far more efficient.

What I'd Do Differently at Scale

At scale (thousands of users, hundreds of domains), I'd:

  1. Learn the domain?belief mappings from data. Instead of hand-authoring the patterns, collect user-validated detections and compute the empirical P(belief | thought, domain). This is a frequency table, not a neural network.

  2. Add hierarchical domains. "Work" ? "Work meetings" ? "Work presentations." Each level refines the belief detection. This is a tree-structured classifier, not a flat map.

  3. Handle domain ambiguity. Some thoughts span domains ("I failed the presentation and my boss was disappointed" - is this work or social?). A soft domain selector (multiple domains with weights) would handle this, approaching true attention.

  4. Add negation awareness. "I'm NOT not good enough" should not match "not good enough." This requires regex instead of indexOf, but it's a 5-line change.

But for the current scale - 7 domains, 13 beliefs, one user at a time - the domain-keyed map is the right architecture. It's 110 lines, runs in the browser, costs nothing, and classifies context-dependent thoughts that a flat classifier would get wrong.

The Full Tool

The live tool lets you:

  • Enter a negative automatic thought
  • Select the life domain where it came up
  • See which core belief is activated (with confidence + reframe)
  • See what the same thought would detect in other domains

It's part of a CBT toolkit of 28 free interactive tools - all vanilla JavaScript, all running client-side, all storing data in localStorage. No framework, no backend, no signup, no AI.

The cognitive distortion checker (context-free flat classification) and the core belief guide (with the downward arrow technique) are complementary tools - the distortion checker tells you how you're thinking wrong, the context-dependent detector tells you what deep belief that thought is activating, and the guide gives you the full protocol for restructuring that belief.


The insight from building this: context is a feature, not a bug. The same thought meaning different things in different contexts isn't a problem to solve with more sophisticated NLP - it's a signal to capture with a simpler architecture. When the context space is small and known, a dropdown + a lookup table beats attention.

Top comments (0)