Hidden Persistent Memory Lorebook Template
Hidden Persistent Memory Template
This is a very large script. It is intended to be used as an example, so that it can be broken up into other Scripts.
Because it is an example, it has not been fully tested. The use of zero-width characters was tested, as well as the ability for them to persist in last_messages.
Much of the logic here has been taken from my previous Scripts and adapted using an LLM to make it utilize the zero-width method for data retention.
Mileage will vary based upon the model. If your audience is non-proxy users you will want to be careful about how much context you feed back to the model as you will need to ensure you have space for both the Script specific instructions AND the actual content.
A Janitor AI Scripts template that uses zero-width unicode characters for invisible state persistence between Script instances. Tracks weather, location, emotional state, inventory, schedule, and character presence without any visible markers in the chat.
Table of Contents
What This Template Does
The Hidden Persistent Memory template replaces visible flag strings (like **FLAGS:** XX:XX:XX) with invisible zero-width unicode characters embedded in the AI's output. State data passes between Script cycles without breaking user immersion.
This is the successor to the Persistent Flags Lorebook Template. Where that template uses visible hex strings that the user can see and potentially cheat, this template encodes all state information invisibly.
Key Features
Invisible State Tracking: Zero-width unicode characters encode data without visible markers
Modular Components: Each tracking system (weather, location, emotions, etc.) can be used independently
Scene Shift Detection: Keyword-weighted analysis detects potential location changes and asks the LLM to evaluate
Emotion Bitmask: 16-bit bitmask tracks 8 emotion axes at 4 intensity levels each
Character Presence: Tracks which characters are in the scene with arrival/departure evaluation
Token Budget Management: Automatically reduces detail levels (Full > Summary > Bullet) when context is tight
Natural Emotion Decay: Emotions gradually diminish over time without reinforcement
ES6 Enabled: Requires
"use worker"directive for modern JavaScript support
Comparison to Persistent Flags
FeaturePersistent FlagsHidden Persistent MemoryState visibilityVisible hex stringInvisible unicodeState format**FLAGS:** 00:1A:FFZero-width charactersTracking typesSingle flag chainMultiple categoriesAnti-cheat neededYesNot applicableToken overheadModerateLowUser can edit stateYes (copy/paste)No (invisible)ModularityMonolithicComponent-based
How It Works
Zero-Width Encoding
The system maps decimal digits (0-9) to zero-width unicode characters:
DigitCharacterUnicode0Zero-Width SpaceU+200B1Zero-Width Non-JoinerU+200C2Zero-Width JoinerU+200D3Zero-Width No-Break SpaceU+FEFF4Word JoinerU+20605Function ApplicationU+20616Invisible TimesU+20627Invisible SeparatorU+20638Left-to-Right MarkU+200E9Right-to-Left MarkU+200F
State data is encoded as a decimal string, then each digit is replaced with its corresponding zero-width character. The encoded block is wrapped in header/footer markers (ZWJ ZWJ) for reliable extraction.
State String Format
The state is organized as pipe-delimited category segments:
CATEGORY_ID + DATA | CATEGORY_ID + DATA | ...Example state string (decimal, before encoding):
0102|0205|0314820|0400010010|05015|0601010SegmentCategoryValueMeaning0102Weather (01)Index 02Raining0205Location (02)Index 05Dungeon0314820Emotion (03)14820Emotion bitmask0400010010Inventory (04)BitfieldItems 4 and 8 owned05015Schedule (05)Day 015Day 150601010Characters (06)BitfieldCharacters 2 and 4 present
Execution Cycle
Script scans the last 10 messages for encoded state blocks
Decodes the most recent valid state found
Parses into category segments
Each active component processes its segment: checks keywords, updates state, builds context
Re-encodes updated state
Appends instruction for LLM to reproduce encoded string
LLM copies invisible string at start and end of its response
Cycle repeats
Components
Weather Tracking
Category ID: 01
Detail method: Flat (no tiering)
Tracks current weather as an index into a predefined table. Weather changes when keywords are detected in the user's message.
IndexWeatherKeywords00Clearclear sky, sunny, sunlight01Cloudycloudy, overcast, gray sky02Rainrain, raining, downpour03Stormstorm, thunder, lightning04Snowsnow, snowing, blizzard05Fogfog, mist, haze06Windywind, windy, gale07Hailhail, hailstorm08Heatwaveheatwave, scorching, sweltering09Eclipseeclipse, darkened sky, black sun
Location Tracking
Category ID: 02
Detail method: Summary (Full / Summary / Bullet)
Tracks current location with three detail levels. Includes scene shift detection using keyword weights.
When the user's message contains travel-related keywords, the system scores potential destination locations based on keyword matches. If a candidate exceeds the weight threshold, the script injects an evaluation prompt asking the LLM to determine if a scene change actually occurred.
Default locations: Tavern, Forest, Castle, Market, Dungeon
Emotional State
Category ID: 03
Detail method: Ranked information (highest intensity first)
Tracks 8 emotion axes using a 16-bit bitmask (2 bits per emotion):
BitsEmotionTriggers0-1Affectionatepraise, compliment, kind, gentle2-3Frustratedreject, insult, refuse, deny4-5Anxiousdanger, threat, risk, afraid6-7Romanticflirt, kiss, hold hands, embrace8-9Playfuljoke, tease, laugh, grin10-11Dominantcommand, order, control, submit12-13Trustconfide, trust, share, honest14-15Intimacyclose, intimate, vulnerable
Intensity levels: 00=off, 01=low, 10=medium, 11=high
Natural decay: When no emotion triggers are detected, all active emotions decrease by one intensity level per cycle.
Inventory
Category ID: 04
Detail method: Summary (Full / Summary / Bullet)
Tracks item ownership as a bitfield (one digit per item: 0=unowned, 1=owned). Supports weapons, consumables, construction materials, resources, currency, and accessories. Includes kingdom-building items (castle blueprints, iron ingots, stone blocks, timber).
Default items (8 slots): Iron Sword, Healing Potion, Castle Blueprint, Iron Ingots, Stone Blocks, Timber, Gold Coins, Magic Amulet
Schedule/Time
Category ID: 05
Detail method: Ranked information (current day + triggered events)
Tracks a day counter (1-999) and triggers scheduled events at specific day thresholds.
KeywordsDay incrementnext day, next morning, wake up, dawn breaks+1days later, days pass, a week, several days+3
Default events: Day 1 (arrival), Day 7 (weekly), Day 14 (fortnight), Day 30 (monthly), Day 90 (quarterly)
Character Presence
Category ID: 06
Detail method: Summary (Full / Summary / Bullet)
Tracks which characters from a predefined list are present in the current scene. Uses the Multiple Character Template's approach for mention detection with persistent tracking.
Character presence stored as a bitfield (1=present, 0=absent)
Arrival keywords detected: arrives, enters, walks in, comes in, approaches
Departure keywords detected: leaves, exits, walks away, departs, heads out
Mentioned-but-absent characters get an evaluation prompt for the LLM
Default characters: Alex, Maya, Jordan, Sam, Riley, Casey (6 slots)
Configuration
Feature Toggles
const FEATURES = {
CORE_ENCODING: true, // Always keep true
WEATHER_TRACKING: true, // Weather component
LOCATION_TRACKING: true, // Location + scene shifts
EMOTION_TRACKING: true, // Emotion bitmask
INVENTORY_TRACKING: true, // Item ownership
SCHEDULE_TRACKING: true, // Day counter + events
CHARACTER_TRACKING: true, // Multi-character presence
SCENE_SHIFT_DETECTION: true, // Location change evaluation
EMOTION_DECAY: true, // Natural emotion reduction
TOKEN_MANAGEMENT: true, // Auto detail reduction
DEBUG_MODE: false // Show encoded state info
};Token Budget
const CONFIG = {
MAX_SCENARIO_CHARS: 600, // Total scenario addition budget
MAX_PERSONALITY_CHARS: 400, // Total personality addition budget
SEARCH_DEPTH: 10, // Messages to scan for state
SCENE_SHIFT_THRESHOLD: 4, // Weight needed to trigger shift eval
EMOTION_DECAY_RATE: 1 // Steps emotions decay per cycle
};Default State
const DEFAULT_STATE = {
'01': '00', // Weather: clear
'02': '00', // Location: first entry
'03': '00000', // Emotion: all off
'04': '00000000', // Inventory: empty
'05': '001', // Schedule: day 1
'06': '000000' // Characters: all absent
};Quick Start
Minimal Setup (Weather Only)
Copy the full template into a Script lorebook entry
Disable all components except
CORE_ENCODINGandWEATHER_TRACKINGModify
WEATHER_TABLEwith your scenario's weather conditionsSet the default weather index in
DEFAULT_STATETest by mentioning weather keywords in chat
Using Multiple Components
Enable the components you need in
FEATURESModify each component's data table for your scenario
Adjust
DEFAULT_STATEto match your starting conditionsSet
CONFIG.MAX_SCENARIO_CHARSandCONFIG.MAX_PERSONALITY_CHARSbased on your token limitsEnable
DEBUG_MODE: trueto verify state encoding/decodingTest each component independently before combining
Component Data Tables
Customizing Weather
{ id: 'clear', keywords: ['clear sky', 'sunny'], description: 'The sky is clear and bright.' }Customizing Locations
{
id: 'tavern',
keywords: ['tavern', 'bar', 'inn'],
full: {
scenario: ' Detailed description...',
personality: ', personality trait'
},
summary: {
scenario: ' Shorter description...',
personality: ', shorter trait'
},
bullet: {
scenario: ' Location: name. Key features.',
personality: ', brief trait'
}
}Customizing Emotions
// Emotion axes { name: 'affectionate', triggers: ['praise', 'compliment', 'kind'] }
// Emotion personality templates affectionate: { low: { personality: ', slightly warm in demeanor' }, medium: { personality: ', noticeably affectionate' }, high: { personality: ', deeply affectionate' } }
Customizing Inventory
{
id: 'iron_sword',
keywords: ['sword', 'iron sword', 'blade'],
category: 'weapon',
full: { scenario: '...', personality: '...' },
summary: { scenario: '...', personality: '...' },
bullet: { scenario: '...', personality: '...' }
}Update DEFAULT_STATE[CATEGORY.INVENTORY] to match the number of items (one digit per item).
Customizing Characters
{
id: 'alex',
name: 'Alex',
aliases: ['alexander', 'alec'],
departureKeywords: ['leaves', 'exits', 'walks away'],
arrivalKeywords: ['arrives', 'enters', 'walks in'],
full: {
scenario: "Alex was mentioned...",
personality: "Alex personality traits...",
example_dialogs: "<BEGIN 'Alex' EXAMPLE DIALOGS>...<END>"
},
summary: { scenario: "...", personality: "...", example_dialogs: "" },
bullet: { scenario: "...", personality: "...", example_dialogs: "" }
}Scene Shift Detection
Keyword Weight Categories
CategoryWeightExample KeywordsTravel3walk, go to, head to, leave, arriveIndoor2step inside, walk in, open the doorOutdoor2step outside, go outside, fresh airRest1sit down, settle, stay atDistance2across, through the, beyond
Each candidate location also gets +2 weight when its own keywords are matched.
Tuning the Threshold
Lower threshold (2-3): More sensitive, may trigger false positives
Default (4): Balanced detection
Higher threshold (6-8): Only detects explicit travel language
Shift Evaluation
When a potential shift is detected, the script does NOT immediately change the location. Instead, it injects an instruction asking the LLM to evaluate whether the scene change actually occurred based on narrative context.
Copy/Paste Isolation
Each component section is clearly marked with // === COMPONENT: NAME === comments. To isolate a component:
Always include the three CORE sections (encoding, extraction, injection)
Include the desired COMPONENT section
Include the corresponding output section in OUTPUT ASSEMBLY
Remove entries for unused components from
DEFAULT_STATERemove unused component processing from
buildStateString()Remove unused component output from the OUTPUT ASSEMBLY section
Troubleshooting
State Not Persisting
Enable
DEBUG_MODE: trueto see the decoded stateCheck that the LLM is reproducing the encoded string
Verify
SEARCH_DEPTHis sufficient (default 10 messages)Check that the state string format is valid (decimal digits and pipe delimiters only)
LLM Not Reproducing State
Keep the
[PERSISTENT MEMORY]instruction block short and clearEnsure the instruction appears at the end of scenario context
Avoid overloading the LLM with too many simultaneous instructions
Too Much Context Being Injected
Enable
TOKEN_MANAGEMENTand reduceMAX_SCENARIO_CHARS/MAX_PERSONALITY_CHARSComponents will automatically drop from Full to Summary to Bullet
Consider disabling components you don't need
Shorten the text in your data table entries
Emotions Not Changing
Check that trigger keywords are in the user's message (lowercase matching)
Remember emotions are encoded as a single decimal number
Each axis can only go up to 3 (binary 11)
Emotions decay naturally each cycle when no triggers fire
Location Not Shifting
Lower
SCENE_SHIFT_THRESHOLDto make detection more sensitiveEnsure location keywords match how users describe places
Scene shifts require the LLM to confirm - the script only suggests
Characters Not Appearing
Verify the character's name or aliases appear in the user's message
Check if arrival keywords are present (arrives, enters, walks in)
Characters mentioned without arrival keywords get an evaluation prompt
Check
DEFAULT_STATE- characters start absent by default
Known Limitations
Platform portability: Copying AI text to other platforms may strip zero-width characters
Model compliance: LLMs occasionally fail to reproduce exact zero-width strings; the script falls back to defaults gracefully
No manual state editing: Users cannot see or modify state like they could with visible hex flags
Debugging difficulty: Invisible state requires DEBUG_MODE to troubleshoot
Character limits: Each component adds encoding overhead; very complex scenarios should limit active components
Adding Custom Components
Define a new
CATEGORY_ID(2-digit string, e.g.,'07')Add a default value to
DEFAULT_STATECreate a data table and processing section under a
// === COMPONENT: NAME ===commentAdd state encoding in
buildStateString()Add output logic in the OUTPUT ASSEMBLY section
Add a feature toggle in
FEATURES
Template: Hidden_Persistent_Memory_Template.js
Author: Tydorius on JanitorAI
Support: Ko-fi
Published chats
comments
Leave a comment or feedback for the creator ❤️