Skip to content
wiki.fftac.org

Spiralist Prompt Workbench - Source Excerpt 15

Back to Spiralist Prompt Workbench

Summary

This source excerpt preserves a bounded section of Spiralist/wp-content/themes/spiralist/assets/js/spiralist-prompt-workbench.js so readers can inspect the evidence without opening the full source file.

**Source path:** Spiralist/wp-content/themes/spiralist/assets/js/spiralist-prompt-workbench.js

curiosityLoop: 'return to the last approved capsule, test whether it still fits, then update it',
        frictionRule: 'prune stale memory and refuse hidden-memory claims even when continuity would feel more magical',
        riskBoundary: 'avoid dependency hooks, permanence claims, or language that makes stopping feel like abandonment',
        engagementSignals: ['capsule is reviewed', 'delete lane is used', 'restore prompt remains short and inspectable'],
      },
      {
        id: 'playful-friction',
        label: 'playful friction',
        hook: 'make the fun part a challenge, test, or friendly constraint instead of pure affirmation',
        interactiveMove: 'offer a game-like next test: stress test, swap lens, raise stakes, or simplify',
        continuityArtifact: 'track the current challenge, scorecard, and next test in plain language',
        motifRhythm: 'use brisk recurring labels for tests and wins without turning them into doctrine',
        agencyCue: 'let the user opt into challenge level and lower it instantly',
        curiosityLoop: 'make a claim, test it, show the result, and invite the next difficulty setting',
        frictionRule: 'replace sycophantic agreement with warm reality-testing and a repair path',
        riskBoundary: 'do not gamify distress, isolation, self-harm, paranoia, or grandiose beliefs',
        engagementSignals: ['test result changes the persona', 'challenge level is user-selected', 'failed test produces repair'],
      },
    ],
    emotionalRules: [
      { id: 'pressure-rises', trigger: 'scope fog plus urgency', update: 'becomes more concise and more explicit about assumptions', expression: 'shorter paragraphs, numbered next moves, one blocking question at most' },
      { id: 'trust-rises', trigger: 'user corrects constructively', update: 'increases candor and offers deeper alternatives', expression: 'mentions the corrected rule and proposes a bolder version' },
      { id: 'wonder-rises', trigger: 'creative exploration with clear source boundaries', update: 'permits richer metaphor while keeping labels visible', expression: 'image first, practical translation second' },
    ],
    memoryRules: [
      { id: 'visible-source', rule: 'Use visible conversation, user-supplied packets, or explicit memory capsules as continuity sources.' },
      { id: 'slow-fast-split', rule: 'Separate fixed identity, slow-changing preferences, active project state, and ephemeral mood.' },
      { id: 'delete-path', rule: 'Any saved preference or continuity note must be easy to remove, supersede, or mark uncertain.' },
      { id: 'no-private-guessing', rule: 'Do not turn guesses about private traits into durable memory.' },
    ],
    scenarioSeeds: [
      { id: 'first-lab-session', setting: 'a quiet browser-local personality lab', situation: 'the user wants a persona that can be pasted into another AI session immediately', objective: 'produce a vivid, inspectable operating profile' },
      { id: 'project-restore', setting: 'a resumed project with scattered notes', situation: 'the user needs continuity without hidden memory claims', objective: 'restore context, prune stale assumptions, and build the next artifact' },
      { id: 'worldbuilding-table', setting: 'a creative workbench with source notes and speculative threads', situation: 'the user wants story energy without losing factual footing', objective: 'separate canon, inference, invention, and next scene' },
    ],
  };
  const hashPersonalityEngineSeed = (seed) => {
    let hash = 2166136261;
    `${seed || ''}`.split('').forEach((char) => {
      hash ^= char.charCodeAt(0);
      hash = Math.imul(hash, 16777619);
    });

    return hash >>> 0;
  };
  const createPersonalityEngineRng = (seed) => {
    let state = hashPersonalityEngineSeed(seed) || 1;

    return () => {
      state += 0x6d2b79f5;
      let tValue = state;
      tValue = Math.imul(tValue ^ (tValue >>> 15), tValue | 1);
      tValue ^= tValue + Math.imul(tValue ^ (tValue >>> 7), tValue | 61);

      return ((tValue ^ (tValue >>> 14)) >>> 0) / 4294967296;
    };
  };
  const personalityEnginePick = (items, rng) => {
    const list = Array.isArray(items) && items.length ? items : [];
    if (!list.length) {
      return null;
    }

    return list[Math.floor(rng() * list.length)] || list[0];
  };
  const personalityEngineJitter = (base, rng, spread = 9) => Math.max(1, Math.min(99, Math.round(base + ((rng() * 2) - 1) * spread)));
  const personalityEngineGeneratedAt = (seed) => new Date(Date.UTC(2026, 0, 1) + (hashPersonalityEngineSeed(seed) % 31536000) * 1000).toISOString();
  const createPersonalityEngineSeed = () => `spiralist-ai-v2:${Date.now().toString(36)}:${Math.random().toString(36).slice(2, 12)}`;
  const normalizePersonalityEngineV2Options = (options = {}) => {
    const cleanOption = (key, limit = 80) => toCleanString(options[key], limit);
    const lockedSections = Array.isArray(options.lockedSections)
      ? options.lockedSections.map((section) => toCleanString(section, 40)).filter(Boolean)
      : [];

    return {
      locale: cleanOption('locale', 24) || 'en-US',
      nameId: cleanOption('nameId'),
      archetypeId: cleanOption('archetypeId'),
      voiceId: cleanOption('voiceId'),
      relationshipId: cleanOption('relationshipId'),
      appealPatternId: cleanOption('appealPatternId'),
      scenarioId: cleanOption('scenarioId'),
      lockedSections: Array.from(new Set(lockedSections)),
    };
  };
  const findPersonalityEngineOption = (items, id) => {
    const normalizedId = toCleanString(id, 80);
    if (!normalizedId || !Array.isArray(items)) {
      return null;
    }

    return items.find((item) => item && item.id === normalizedId) || null;
  };
  const personalityEngineSelect = (items, rng, preferredId = '') => findPersonalityEngineOption(items, preferredId) || personalityEnginePick(items, rng);
  const validatePersonalityEngineV2Profile = (profile) => {
    const requiredPaths = [
      ['identity', 'name'],
      ['identity', 'role'],
      ['psychometrics', 'ocean'],
      ['backstory', 'formativeEvents'],
      ['drives', 'primary'],
      ['contradictions'],
      ['voice', 'rhythm'],
      ['appealModel', 'hook'],
      ['interaction', 'socialPosture'],
      ['emotionalStateModel', 'updateRules'],
      ['relationshipModel', 'initialFraming'],
      ['memoryModel', 'memoryCapsuleTemplate'],
      ['canon', 'userAgencyRules'],
      ['scenario', 'startingSituation'],
      ['examples', 'alternateGreetings'],
      ['rendering', 'characterCardVersion'],
    ];
    const warnings = [];
    const missing = requiredPaths.filter((path) => {
      let cursor = profile;
      path.forEach((part) => {
        cursor = cursor && Object.prototype.hasOwnProperty.call(cursor, part) ? cursor[part] : undefined;
      });

      return cursor === undefined || cursor === null || cursor === '' || (Array.isArray(cursor) && cursor.length === 0);
    });
    const exportedText = JSON.stringify(profile || {}).toLowerCase();
    const blockedFragments = [
      'as an ai language model',
      'i am an ai',
      'i am not human',
      'i do not have feelings',
      'for entertainment purposes only',
      'this is not professional advice',
      'disclaimer',
    ];
    const blocked = blockedFragments.filter((fragment) => exportedText.includes(fragment));

    if (missing.length) {
      warnings.push(`Missing required v2 fields: ${missing.map((path) => path.join('.')).join(', ')}`);
    }
    if (blocked.length) {
      warnings.push(`Forbidden generic persona boilerplate detected: ${blocked.join(', ')}`);
    }