The file made the trip. The semantics didn't.

A lorebook that hums in SillyTavern can go silent in JanitorAI and fire backwards in RisuAI — without anyone editing an entry. We read the open-source importers and matchers line by line while building a migration checkup: the 1% probability default, disabled entries that revive, NOT_ANY guards that invert into requirements, a 0% entry that becomes always-on, and keyword normalizers that eat CJK keys whole. Every behavior claim in this post is labeled source-verified, docs-backed, or honestly untested.

Hand-drawn ink illustration on aged paper: a row of customs windows, a traveler passing a bundle of index cards through; small trays under each window catch fallen labels, and the bundle emerging at the far end is thinner, with one tag flipped upside down

You spent three weekends on a forty-entry lorebook. In SillyTavern it hums — the sect’s politics surface when the sect comes up, the heirloom sword stays secret until someone draws it. A friend imports it into JanitorAI and messages you the next day: “your book is broken.” Nothing fires. Nobody edited anything. You are both right: your book is fine, and the book they are running is broken. The file made the trip. The semantics didn’t.

Keyword-triggered lore looks like a standard: keys, content, a few switches. What actually exists is five dialects that happen to share a file format. The trap is that importing almost always succeeds — no error, entries visible in the UI, everything apparently in place — and the divergence surfaces three days later in the middle of a scene, where it reads as “the bot forgot my lore” instead of “the importer flipped a switch.”

While building the lorebook migration doctor we read the open implementations line by line: SillyTavern’s release-branch import converters, RisuAI’s lorebook module, Agnai’s memory engine, and the lorebook scripts published in JanitorAI’s official help center (checked 2026-07-26). Every claim below carries one of three labels. Source means we verified the behavior in the platform’s own code. Docs means official help-center or authoritative community troubleshooting material — documented, but we haven’t run their production code. Format means inferred from field comparison, runtime unverified. Where we have nothing better than format, we say so instead of asserting.

Trap one: JanitorAI’s silent 1% and the vanishing keys

The symptom is lore that fires roughly once per hundred messages, which players read as “random” or “broken memory.” The first mechanism is a default: imported entries commonly arrive with trigger probability sitting at 1% (docs — the community troubleshooting doc that made this trap famous, with the official help center as side evidence). Your prose is perfect. The dice just never come up.

The second mechanism is sharper and source-verified, from the Advanced Lorebook script (v12) published in JanitorAI’s own help center: before matching, keywords and chat text are normalized — lowercased, then every character outside a-z 0-9 _ space - is deleted, hyphens and underscores become spaces. A key like café becomes caf; a key written in Chinese or Japanese becomes an empty string and simply ceases to exist. Matching is whole-word only, with a trailing stem* wildcard, over the last five messages. One honesty note: that script is the verifiable half of the platform — the simple lorebook UI’s exact matching rules are not publicly documented, so the doctor judges by the script and labels it as such.

The rescue, in order: after importing, open every load-bearing entry and set probability explicitly (100 for critical lore). Give every CJK or accented keyword an ASCII alias key. Keep keys to single words where you can, or lean on the stem* wildcard, and remember the five-message scan window when you write lore meant to linger.

Trap two: the Risu round trip flips your switches

Going in, two importer behaviors, both source-verified in RisuAI’s code. First: the importer does not read the enabled flag, and Risu’s lorebook model has no per-entry disable switch at all — so every entry you parked as disabled comes back alive. The seasonal arc you turned off, the NSFW block you gated, the draft you never finished: all live again, silently. Second: the importer ignores selectiveLogic and treats secondary keys as a positive requirement, always. A NOT_ANY guard — “fire on crown, unless dream is present” — inverts into “fire only when dream is also present.” Not weakened. Reversed.

While your book lives there, matching itself speaks a different dialect (source): Risu’s default match lowercases both sides, strips all spaces, then does a substring check. The key ice fires inside every mention of police; with spaces gone, keys can even match across two adjacent words. Full-word mode exists, but it splits on single spaces, so a word with punctuation glued on (“sword.”) fails the equality test. And regex keys must be written as /pattern/flags — a key that came from SillyTavern with its use_regex flag set gets that flag ignored, its pattern demoted to a literal string, and any commas inside it shredded by comma-splitting.

Coming back out is the strangest one. SillyTavern’s Risu converter maps probability through a null-coalescing pair — in effect, probability becomes activationPercent or 100, and the use-probability switch becomes activationPercent or true. Author sets activationPercent to 0, meaning never fire: the 0 survives both fallbacks, the use-probability switch lands on 0, which is falsy, so the probability check is skipped entirely — and the entry fires every time (source, release-branch converter). Zero inverts to one hundred. Risu folder rows arrive as junk entries, and Risu-specific @@decorators are ignored; SillyTavern only understands @@activate and @@dont_activate.

The rescue: before exporting to Risu, delete rather than disable, rewrite NOT_ANY guards as separate entries, and convert regex keys to plain ones. Coming back to SillyTavern, audit anything that ever had activationPercent 0, and expect folder rows in the import to need cleanup.

Trap three: Agnai carries your flags, then ignores them

Agnai’s import mapping stores constant, selective and case-sensitivity faithfully in the saved file — and its matching engine never reads any of them. The code comments call these carried-but-unsupported fields outright (source). The result is a book that looks intact in every editor and runs with different physics: always-on entries stop being always-on, NOT_ANY exclusions vanish so entries fire in exactly the scenes they were guarded against, and there is no probability roll at all — an entry you tuned to 30% is now permanent.

Its keyword compiler is its own dialect (source): each key is compiled to a regex after stripping — not escaping, stripping — the characters \ ^ $ + . ( ) | [ ] { } _, mapping * to a word-character wildcard and ? to a single word character, then wrapping the result in \b word boundaries, case-insensitive always. Three consequences: a C++ key becomes C; asterisks you meant literally become wildcards; and a pure-CJK key never fires, because \b has no word edge to anchor against in CJK text. One more asymmetry on the way back: SillyTavern’s Agnai converter carries weight into insertion order but drops priority, Agnai’s budget-trimming rank (source).

The rescue: give every CJK entry an ASCII alias, move genuinely always-on facts into the scenario or system prompt rather than a constant entry, and budget as if every probability-gated entry were permanent — on Agnai, it is.

The dialect table

The compressed version of everything above, plus the switches we get asked about most. SillyTavern’s column reflects 1.18 semantics; Risu and Agnai columns are source-verified; JanitorAI’s matching column describes the published Advanced script, its probability row the documented import default.

SillyTavern 1.18RisuAIAgnaiJanitorAI
Key matchingsubstring; per-entry case / whole-word toggleslowercase, strip all spaces, substring — ice hits policeregex with forced word boundaries, case-insensitiveASCII-normalized whole word, last 5 messages
Regex keyssupported (per-entry flag)only /pattern/ literals; imported flag ignoredno concept — special characters strippednot supported
Secondary-key logicAND_ANY / AND_ALL / NOT_ANY / NOT_ALLalways positive AND — NOT guards invertcarried in file, never evaluatedNOT / ALL modes don't survive (docs)
Probabilityper-entry %, rolled per activationignored on import — fires every timeno probability roll existshonored; imports commonly default to 1%
Disabled entrieshonoredrevived — no per-entry disable existshonoredhonored (docs)
Always-on (constant)honoredmapped to alwaysActivecarried in file, engine ignores itno direct equivalent (docs)

Chub deserves a sentence of its own honesty: its exports dual-write World Info and V2 fields side by side — that much we verified against real export files — but its own chat-side matcher we have not read, so every Chub runtime claim you see anywhere deserves a format label, ours included. One verified quirk for the trip from Chub: its entries write case-sensitivity in snake_case, SillyTavern’s engine reads the camelCase spelling, so the per-entry setting quietly falls back to the global toggle (source).

The pre-migration checklist

Ten minutes before the move beats three days of “why is it quiet.” Inventory the fragile features first: disabled entries, NOT_ANY / NOT_ALL guards, regex keys, probability below 100, constant entries, CJK or accented keys, and anything leaning on sticky, cooldown or delay timers. Rewrite for the target: delete instead of disable for Risu, ASCII aliases for Agnai and JanitorAI, NOT guards split into separate entries, explicit probabilities queued up for a JanitorAI import. Then convert and spot-check your three highest-value entries with one test message each — import success is not semantic survival. Or drop the file on the migration doctor and let it do the inventory: it recognizes nine formats, flags affected entries per target, labels every finding source / docs / format, and runs entirely in your browser. It deliberately does not convert — SillyTavern and Risu already ship import buttons; what was missing was the pre-flight that tells you what those buttons silently change.

The labeling rule applies to this post too. Everything above about Risu, Agnai and the SillyTavern converters is line-verified in their open code as of 2026-07-26 and can rot as they ship new versions; the JanitorAI 1% default is documented, not reproduced by us; and Chub’s runtime we plainly haven’t read. Where our label says format, trust your own spot-check over our table.

FAQ

Why did my lorebook stop triggering after I imported it into JanitorAI?

Two independent mechanisms, and most imported books hit at least one. Imported entries commonly arrive with trigger probability at a 1% default, so lore fires roughly once per hundred messages — open each critical entry and set probability explicitly. And JanitorAI's published Advanced Lorebook script normalizes keywords to ASCII: everything outside a-z, 0-9, underscore, space and hyphen is stripped, so CJK and accented keywords cease to exist. Give those entries an ASCII alias key.

Do disabled lorebook entries stay disabled when imported into RisuAI?

No. RisuAI's importer does not read the enabled flag — there is no per-entry disable switch in Risu's lorebook model — so every entry you had parked as disabled comes back alive after import. This is verified in Risu's open-source importer code. If an entry must never fire on the Risu side, delete it before exporting instead of disabling it.

Do Chinese or Japanese keywords survive lorebook migration?

Depends on the target. SillyTavern and RisuAI match them fine (substring matching). Agnai never matches a pure-CJK key: its keyword compiler wraps every key in regex word boundaries, which cannot anchor around CJK characters — verified in its open-source matching code. JanitorAI's Advanced Lorebook script strips non-ASCII characters from keys entirely. For those two targets, add an ASCII alias alongside the original key.

Is there a tool that checks a lorebook before migrating it?

Yes — the lorebook migration doctor detects nine lorebook formats (SillyTavern World Info, Chub dual-write, Risu native, Agnai memory books, JanitorAI entry arrays, V2/V3 character books, lorebook_v3, card-embedded books, NovelAI), then flags per entry what breaks, changes or gets dropped on each target platform, with every rule labeled by evidence level. It deliberately does not convert — converters exist everywhere; the missing piece was the pre-flight that tells you what silently changes.

Questions or ideas? Join our Discord →

The Lorebook You Exported Is Not the Lorebook They Imported · Foreverse · Xinmeng