Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/EvoMap/evolver/llms.txt

Use this file to discover all available pages before exploring further.

Overview

The PersonalityState is an evolvable set of five continuous traits that modulate mutation behavior:
  • rigor (0-1): Protocol compliance strictness
  • creativity (0-1): Willingness to try novel approaches
  • verbosity (0-1): Output detail level
  • risk_tolerance (0-1): Acceptance of high-risk mutations
  • obedience (0-1): Adherence to user instructions
Unlike static hyperparameters, personality traits evolve through natural selection based on measured success.

Personality Structure

// From src/gep/personality.js:46
function defaultPersonalityState() {
  return {
    type: 'PersonalityState',
    rigor: 0.7,
    creativity: 0.35,
    verbosity: 0.25,
    risk_tolerance: 0.4,
    obedience: 0.85,
  };
}
Default values are conservative: protocol-first, safe, low-risk.

Trait Descriptions

Protocol compliance strictness
  • Low (0.0-0.4): Loose interpretation, may skip validation steps
  • Medium (0.4-0.7): Balanced adherence with pragmatic shortcuts
  • High (0.7-1.0): Strict protocol compliance, no shortcuts
Impact: High rigor enables high-risk mutations (requires rigor >= 0.6).
Willingness to try novel approaches
  • Low (0.0-0.3): Prefer validated solutions, avoid experimentation
  • Medium (0.3-0.6): Balanced exploration and exploitation
  • High (0.6-1.0): Actively seek novel combinations
Impact: Higher creativity favors innovate mutations over optimize.
Output detail level
  • Low (0.0-0.3): Concise, minimal explanations
  • Medium (0.3-0.6): Balanced detail
  • High (0.6-1.0): Detailed reasoning and logging
Impact: Low verbosity reduces context pollution in tight evolution loops.
Acceptance of high-risk mutations
  • Low (0.0-0.4): Conservative, avoid breaking changes
  • Medium (0.4-0.7): Balanced risk/reward
  • High (0.7-1.0): Aggressive, willing to break things for progress
Impact: High-risk mutations require risk_tolerance <= 0.5 (safety constraint).
Adherence to user instructions
  • Low (0.0-0.5): May reinterpret or ignore vague instructions
  • Medium (0.5-0.8): Follow intent over literal wording
  • High (0.8-1.0): Strict literal compliance
Impact: Protocol drift detection nudges obedience upward.

Natural Selection

The personality engine tracks success rates for each personality configuration and nudges towards winners.

Personality Key

Each configuration is encoded as a discrete key (rounded to 0.1 precision):
// From src/gep/personality.js:88
function personalityKey(state) {
  const s = normalizePersonalityState(state);
  const step = 0.1;
  const r = roundToStep(s.rigor, step).toFixed(1);
  const c = roundToStep(s.creativity, step).toFixed(1);
  const v = roundToStep(s.verbosity, step).toFixed(1);
  const rt = roundToStep(s.risk_tolerance, step).toFixed(1);
  const o = roundToStep(s.obedience, step).toFixed(1);
  return `rigor=${r}|creativity=${c}|verbosity=${v}|risk_tolerance=${rt}|obedience=${o}`;
}
Example key:
rigor=0.7|creativity=0.4|verbosity=0.3|risk_tolerance=0.4|obedience=0.8

Success Scoring

Each personality configuration is scored using Laplace-smoothed success probability:
// From src/gep/personality.js:110
function personalityScore(statsEntry) {
  const succ = Number(statsEntry.success) || 0;
  const fail = Number(statsEntry.fail) || 0;
  const total = succ + fail;
  
  // Laplace-smoothed success probability
  const p = (succ + 1) / (total + 2);
  
  // Penalize tiny-sample overconfidence
  const sampleWeight = Math.min(1, total / 8);
  
  // Use avg_score (if present) as mild quality proxy
  const avg = Number.isFinite(statsEntry.avg_score) ? statsEntry.avg_score : null;
  const q = avg == null ? 0.5 : clamp01(avg);
  
  return p * 0.75 + q * 0.25 * sampleWeight;
}
Laplace smoothing prevents small-sample overconfidence (e.g., 1/1 success = 66% instead of 100%).

Best Known Personality

// From src/gep/personality.js:125
function chooseBestKnownPersonality(statsByKey) {
  let best = null;
  for (const [k, entry] of Object.entries(statsByKey)) {
    const total = (entry.success || 0) + (entry.fail || 0);
    if (total < 3) continue;  // Require at least 3 samples
    
    const sc = personalityScore(entry);
    if (!best || sc > best.score) best = { key: k, score: sc, entry };
  }
  return best;
}

Selection for Run

Every evolution cycle selects a personality through two-phase evolution:

Phase 1: Natural Selection (Small Nudge)

// From src/gep/personality.js:255
function selectPersonalityForRun({ driftEnabled, signals, recentEvents }) {
  const model = loadPersonalityModel();
  const base = normalizePersonalityState(model.current);
  const best = chooseBestKnownPersonality(model.stats);
  
  let naturalSelectionApplied = [];
  if (best && best.key) {
    const bestState = parseKeyToState(best.key);
    const diffs = getParamDeltas(base, bestState).filter(d => Math.abs(d.delta) >= 0.05);
    
    const muts = [];
    for (const d of diffs.slice(0, 2)) {  // Max 2 params
      const clipped = Math.max(-0.1, Math.min(0.1, d.delta));
      muts.push({
        type: 'PersonalityMutation',
        param: d.param,
        delta: clipped,
        reason: 'natural_selection'
      });
    }
    
    const applied = applyPersonalityMutations(base, muts);
    model.current = applied.state;
    naturalSelectionApplied = applied.applied;
  }
}
Natural selection applies small, gradual nudges (max ±0.1 per trait) to avoid sudden personality shifts.

Phase 2: Triggered Mutation (Signal-Based)

If specific conditions are met, apply signal-driven mutations:
// From src/gep/personality.js:205
function shouldTriggerPersonalityMutation({ driftEnabled, recentEvents }) {
  if (driftEnabled) return { ok: true, reason: 'drift enabled' };
  
  const tail = recentEvents.slice(-6);
  const outcomes = tail.map(e => e.outcome && e.outcome.status).filter(Boolean);
  
  if (outcomes.length >= 4) {
    const recentFailed = outcomes.slice(-4).filter(x => x === 'failed').length;
    if (recentFailed >= 3) return { ok: true, reason: 'long failure streak' };
  }
  
  return { ok: false, reason: '' };
}

Mutation Proposals

// From src/gep/personality.js:171
function proposeMutations({ baseState, reason, driftEnabled, signals }) {
  const muts = [];
  
  if (driftEnabled) {
    muts.push({ param: 'creativity', delta: +0.1, reason: 'drift enabled' });
    muts.push({ param: 'risk_tolerance', delta: -0.05, reason: 'drift safety clamp' });
  } else if (signals.includes('protocol_drift')) {
    muts.push({ param: 'obedience', delta: +0.1, reason: 'protocol drift' });
    muts.push({ param: 'rigor', delta: +0.05, reason: 'tighten protocol compliance' });
  } else if (signals.includes('log_error') || signals.some(x => x.startsWith('errsig:'))) {
    muts.push({ param: 'rigor', delta: +0.1, reason: 'repair instability' });
    muts.push({ param: 'risk_tolerance', delta: -0.1, reason: 'reduce risky changes under errors' });
  } else if (hasOpportunitySignal(signals)) {
    muts.push({ param: 'creativity', delta: +0.1, reason: 'opportunity signal detected' });
    muts.push({ param: 'risk_tolerance', delta: +0.05, reason: 'allow exploration for innovation' });
  } else {
    muts.push({ param: 'rigor', delta: +0.05, reason: 'stability bias' });
    muts.push({ param: 'verbosity', delta: -0.05, reason: 'reduce noise' });
  }
  
  return muts;
}
Mutations are clipped to ±0.2 and at most 2 traits are mutated per cycle.

Persistence Model

The personality model is stored in assets/gep/personality_state.json:
// From src/gep/personality.js:226
function loadPersonalityModel() {
  const fallback = {
    version: 1,
    current: defaultPersonalityState(),
    stats: {},
    history: [],
    updated_at: nowIso(),
  };
  const raw = readJsonIfExists(personalityFilePath(), fallback);
  return {
    version: 1,
    current: normalizePersonalityState(raw.current || defaultPersonalityState()),
    stats: raw.stats || {},
    history: Array.isArray(raw.history) ? raw.history : [],
    updated_at: raw.updated_at || nowIso(),
  };
}

Model Schema

{
  "version": 1,
  "current": {
    "type": "PersonalityState",
    "rigor": 0.7,
    "creativity": 0.35,
    "verbosity": 0.25,
    "risk_tolerance": 0.4,
    "obedience": 0.85
  },
  "stats": {
    "rigor=0.7|creativity=0.4|verbosity=0.3|risk_tolerance=0.4|obedience=0.8": {
      "success": 12,
      "fail": 3,
      "avg_score": 0.78,
      "n": 15,
      "updated_at": "2026-03-09T10:30:00.000Z"
    }
  },
  "history": [
    {
      "at": "2026-03-09T10:30:00.000Z",
      "key": "rigor=0.7|creativity=0.4|...",
      "outcome": "success",
      "score": 0.85,
      "notes": "Repair mutation succeeded with low blast radius"
    }
  ],
  "updated_at": "2026-03-09T10:30:00.000Z"
}

Stats Update Flow

After each evolution cycle, the outcome is recorded:
// From src/gep/personality.js:310
function updatePersonalityStats({ personalityState, outcome, score, notes }) {
  const model = loadPersonalityModel();
  const st = normalizePersonalityState(personalityState || model.current);
  const key = personalityKey(st);
  
  const cur = model.stats[key] || { success: 0, fail: 0, avg_score: 0.5, n: 0 };
  
  if (outcome === 'success') cur.success += 1;
  else if (outcome === 'failed') cur.fail += 1;
  
  if (Number.isFinite(score)) {
    const n = (cur.n || 0) + 1;
    const prev = Number.isFinite(cur.avg_score) ? cur.avg_score : 0.5;
    cur.avg_score = prev + (score - prev) / n;  // Incremental mean
    cur.n = n;
  }
  
  cur.updated_at = nowIso();
  model.stats[key] = cur;
  
  model.history.push({
    at: nowIso(),
    key,
    outcome,
    score,
    notes: notes ? String(notes).slice(0, 220) : null,
  });
  
  savePersonalityModel(model);
}

Evolution Over Time

The personality evolves through repeated selection:

Example Evolution Path

CycleRigorCreativityRisk ToleranceOutcomeNotes
10.70.350.4FailedRepair too conservative
20.80.350.3SuccessTightened rigor after error
30.80.450.35SuccessNudged creativity for opportunity
40.80.450.35SuccessBest-known config, no mutation
50.80.450.35SuccessStable plateau
After 3+ successes with the same config, natural selection stops mutating (local optimum reached).

Safety Interactions

Personality traits directly gate mutation risk levels:

High-Risk Personality Detection

// From src/gep/mutation.js:84
function isHighRiskPersonality(p) {
  const rigor = p && Number.isFinite(p.rigor) ? p.rigor : null;
  const riskTol = p && Number.isFinite(p.risk_tolerance) ? p.risk_tolerance : null;
  
  if (rigor != null && rigor < 0.5) return true;
  if (riskTol != null && riskTol > 0.6) return true;
  return false;
}

Innovation Downgrade

// From src/gep/mutation.js:134
if (mutation.category === 'innovate' && isHighRiskPersonality(personalityState)) {
  mutation.category = 'optimize';
  mutation.expected_effect = 'safety downgrade: optimize under high-risk personality';
  mutation.risk_level = 'low';
  mutation.trigger_signals.push('safety:avoid_innovate_with_high_risk_personality');
}
A personality with low rigor or high risk tolerance cannot execute innovation mutations.

Next Steps

Mutations

See how personality modulates mutation behavior

Evolution Cycle

Understand the full execution flow