SecretsEnabled is an AI Dungeon script for handling hidden character secrets without exposing raw secret text in normal story context.
Made by Fly3_r
SecretsEnabled keeps character secrets out of normal story context so the AI is much less likely to blurt them out too early. Instead of feeding the secret itself to the model right away, it only feeds the character's hidden behavior. The AI is guided without being directly told to reveal why.
A secret becomes active when the script sees the right character and the right trigger in the scene. Once activated, it stays active for a short time, so the hidden behavior can continue naturally across the next few turns instead of disappearing immediately. If the same character and trigger show up again, that timer refreshes.
While a secret is active, the script also checks whether the scene seems to satisfy the reveal condition. Each time that reveal condition is judged true, the card's Matches progress goes up by one. Once that progress reaches the required number, the real Secret: text is finally allowed into hidden context for the AI to use.
To summarise:
- The secret itself stays hidden.
- Only the hidden behavior is applied at first.
- The secret becomes warm for a few turns after it is triggered.
- The script watches for reveal-condition moments while it is warm.
- After enough matching reveal moments, the real secret is unlocked for the AI.
Within your scenario go to the details tab and select EDIT SCRIPTS, then:
- Inside the
Inputscript screen paste:
const modifier = (text) => {
if (typeof SecretsEnabled !== "function") {
return { text };
}
return { text: SecretsEnabled("input", text) };
};
modifier(text);- Inside the
Contextscript screen paste:
const modifier = (text) => {
if (typeof SecretsEnabled !== "function") {
return { text, stop: false };
}
const currentStop = typeof stop === "undefined" ? false : stop;
const result = SecretsEnabled("context", text, currentStop);
return {
text: result[0],
stop: result[1],
};
};
modifier(text);- Inside the
Outputscript screen paste:
const modifier = (text) => {
if (typeof SecretsEnabled !== "function") {
return { text };
}
return { text: SecretsEnabled("output", text) };
};
modifier(text);- Copy the entire text from
SecretsEnabled/src/Library.js(GitHub link) and paste it into theLibraryscript screen.
The story card should use the custom type Secret, and the body can just contain the key/value lines.
Character: Mira
SecretId: afraid_of_water
Triggers: water, river, lake, ocean, drowning, boat, bridge, storm
Secret: Mira almost drowned as child and now has a phobia of water
Behavior: Avoids getting close to deep water, hesitates before crossing, downplays discomfort, redirects the conversation
Reveal: Do not state the reason unless she is cornered, forced, or directly confronted.
Matches: 3
Aliases: Mira Vale
Supported fields:
CharacterSecretIdSecretTriggersBehaviorRevealMatchesAliasesThreshold
Required fields:
- story card
type: Secret CharacterTriggersBehavior
Notes:
Matchesis the reveal threshold.Stageis still accepted as a legacy fallback.Secret:is only forwarded to the AI after the reveal threshold is reached.- The story card
descriptionfield is now used as a persistent notes area for secret runtime state.
SecretsEnabled uses a dedicated settings story card as the main user-facing config surface.
If use_settings_storycard is enabled in the script defaults and no settings card exists yet, the script will create one automatically using this shape:
SecretsEnabledSettings:
enabled: true
use_settings_storycard: true
activation_duration_in_turns: 3
max_active_secrets: 4
min_trigger_score: 1
history_turns_check: 3
use_ai_reveal_judge: true
fallback_keyword_judge: true
diag_output: false
max_judge_log_entries: 25
The script reads the settings card every turn and rebuilds its runtime config from it.
Important behavior:
- If the settings card exists and
use_settings_storycard: true, the card values are applied. - If the settings card exists and
use_settings_storycard: false, the script falls back to the built-in defaults inLibrary.js. - If no settings card exists and
use_settings_storycardis enabled in the built-in defaults, the script attempts to create one automatically. debug_outputis not read from the settings story card.
Full debugging is controlled only from Library.js.
const DEBUG_OUTPUT = false;When DEBUG_OUTPUT is set to true:
- full debug information is written to the console log
- it is not appended to the player's visible story output
!secretsenabled debugstill returns a visible one-off debug report when you ask for it
These are the built-in defaults in Library.js:
const DEBUG_OUTPUT = false;
const USE_AI_REVEAL_JUDGE = true;
const DEFAULT_CONFIG = {
enabled: true,
use_settings_storycard: true,
activation_duration_in_turns: 3,
max_active_secrets: 4,
min_trigger_score: 1,
history_turns_check: 3,
use_ai_reveal_judge: USE_AI_REVEAL_JUDGE,
fallback_keyword_judge: true,
debug_output: DEBUG_OUTPUT,
diag_output: false,
max_judge_log_entries: 25,
};What they mean:
enabled: master on/off switch for secret behavior injectionuse_settings_storycard: whether the script should use and auto-create the settings story cardactivation_duration_in_turns: how long a triggered secret stays warmmax_active_secrets: maximum number of warm secrets kept and injected into hidden context on a turnmin_trigger_score: minimum number of trigger hits needed to activate a secrethistory_turns_check: how many previous history entries to include when matching secrets and mention recencyuse_ai_reveal_judge: whether reveal progress is driven by the AI judge outputfallback_keyword_judge: whether the keyword-based fallback reveal check is allowed when the AI judge output is missing or malformeddebug_output: internal runtime flag sourced fromconst DEBUG_OUTPUTinLibrary.jsdiag_output: whether a compact diagnostic summary is appended to normal AI responsesmax_judge_log_entries: how many raw judge lines are kept in the secret card notes log before the oldest entries are dropped
AI judge capacity:
- even if
max_active_secretsis higher, only the first 2 active reveal checks use the AI judge - any additional active reveal checks fall back to the keyword heuristic for that turn
Matches means reveal threshold, not reveal state.
In other words:
Matches: 3means the reveal condition must be judged true on 3 separate turns- reveal progress increases by at most 1 per turn
- once progress reaches the threshold, the secret is considered revealed enough to forward
Reveal progress is stored in script state and is also mirrored into the secret card description field so it can persist and be edited by the player.
Each secret card now uses its description field as a notes area for persistent runtime state.
SecretsEnabled preserves any normal user text already in description, then appends a managed JSON block like this:
Test Info
[SecretsEnabledState]
{
"secretId": "afraid_of_water",
"revealProgress": 1,
"active": true,
"turnsRemaining": 2,
"lastTriggeredTurn": 12,
"lastJudgeRaw": "SEJ|afraid_of_water|yes|She feels cornered around the river.",
"judgeLog": [
"SEJ|afraid_of_water|no|She is still avoiding the river.",
"SEJ|afraid_of_water|yes|She feels cornered around the river."
],
"updatedAt": "2026-03-12T10:15:00.000Z"
}
[/SecretsEnabledState]
How it behaves:
- The script loads this block back in every turn before processing secrets.
- In clients that do not persist story card edits from the Output modifier, judge-derived note updates are flushed on the next action.
- If you manually edit the JSON block, the script will use those edited values.
revealProgress,active, andturnsRemainingare the main user-editable fields.judgeLogkeeps a rolling history of rawSEJ|...lines, appending new entries at the end.- Once
judgeLogreachesmax_judge_log_entries, the oldest entries are dropped first. - If you reset or remove the block, the secret state resets from the story card.
- The normal secret definition still lives in the card body (
value/entry/text). - The
descriptionfield is treated as notes, not as the main secret definition body.
By default, reveal progress is driven by an AI-style judge line.
For matched or still-active secrets with a Reveal: field, the script injects a hidden instruction asking the model to begin its output with machine-only prefix lines, then immediately continue into the normal story response, like:
SEJ|afraid_of_her_apartment|yes|She feels directly threatened.
The script strips those prefix lines from visible output, parses them, and if met=yes increments reveal progress by 1 for that turn.
If the model omits or mangles the judge output, the script can fall back to a keyword-based reveal check run against the final turn text if fallback_keyword_judge is enabled.
To keep judge overhead under control:
- only the first 2 active reveal checks use the AI judge on a turn
- any remaining active reveal checks use the keyword heuristic instead
Current rules:
- one reveal increment max per secret per turn
- the judge answers from the character's perspective
- if the answer is unclear, the prompt tells the model to answer
no - if parsing fails or the judge line is missing, the fallback keyword judge may still advance reveal progress
Matched secrets stay active for activation_duration_in_turns turns after a real trigger match.
If the same secret is triggered again before it expires, the timer resets back to the full duration.
While active, a secret can continue to influence behavior even if the latest turn does not contain a fresh trigger hit or a freshly repeated character mention. This helps the behavior persist naturally across adjacent turns while the secret remains warm.
If the number of warm secrets exceeds max_active_secrets, the script prunes them before injection:
- secrets whose characters are directly mentioned in the current player input are kept first
- warm secrets whose characters are not mentioned in the current player input are dropped
- if too many directly mentioned characters remain, the script keeps the most recently mentioned ones based on the recent history window
SecretsEnabled uses an effective turn counter so secret timers behave properly across player actions, continue, and retry.
The current rules are:
- normal player input advances the turn in
Input continueadvances the effective turn inContextretrydoes not advance the effective turn
That means:
turnsRemainingticks down oncontinueturnsRemainingdoes not tick down again onretryrevealProgresscan only advance once for a given effective turn
The detection rule is:
- if there is no
Inputhook andinfo.actionCount > history.length, treat it asretry - if there is no
Inputhook andinfo.actionCount == history.length, treat it ascontinue
When DEBUG_OUTPUT is enabled, the console debug includes:
Current turnDetected action kind
When a secret matches, the script injects hidden behavior guidance into context.
By default this includes:
- behavior lines from
Behavior: - reveal policy guidance from
Reveal: - reveal progress like
2 / 4 - active-turn information while the secret is still warm
If reveal progress has reached the card's threshold and the card has Secret:, the hidden block will also include:
Revealed secret: ...
Recommended commands:
!secretsenabled debug
!secretsenabled on
!secretsenabled off
Supported aliases:
/secretsenabled debug
/secrestsenabled debug
@@secretsenabled debug
Command responses are returned through the Output modifier, so the debug command should appear to the player as plain output text, not only in logs.
There are now two separate diagnostics modes:
diag_output: player-visible compact diagnostics, controlled from the settings story cardDEBUG_OUTPUT: full debug logging to console, controlled fromLibrary.js
When diag_output is enabled, the player sees either a compact summary for each current match:
- Mira / afraid_of_water (triggered) turnsRemaining=3 triggers=[water] presence=[Mira]
Reveal progress: 1 / 3 metThisTurn=yes forwarded=no
AI judge raw: SEJ|afraid_of_water|yes|Directly confronted about going into the water.
Reveal keyword hits: evidence
Pressure hits: reveal
Or, when there are no current matches:
SecretsEnabled: no current matches
When DEBUG_OUTPUT is enabled in Library.js, the full [SecretsEnabled Debug] ... [/SecretsEnabled Debug] report is written to the console log for each turn instead of being appended to player-visible output.
What you can inspect:
- the parsed secret cards
- the settings card status
- the match result
- the behavior block being injected
- reveal progress
- the AI judge response
- whether the literal secret was forwarded
- the final story response
- the console log when
DEBUG_OUTPUTis enabled
What you cannot directly inspect:
- the model's internal chain-of-thought or hidden interpretation of the behavior block
If a secret lingers too long:
- reduce
history_turns_check - or reduce
activation_duration_in_turns
If reveal progress never advances:
- check whether the secret has a
Reveal:line - set
DEBUG_OUTPUT = trueinLibrary.js - inspect
AI judge status,Judge source, andHeuristic fallback metin the console log
If the settings card does not appear:
- make sure
use_settings_storycardistruein the built-in defaults - run any normal do/say/story action so the script gets a turn to initialize

