response_postprocessor

Central response postprocessing pipeline.

Transforms raw LLM output into clean, Discord-friendly text before it is sent to any platform adapter. The pipeline runs every step in order:

  1. Decode literal \uXXXX Unicode escapes emitted by models

  2. Extract and strip <thought> / <thinking> / 💡thought…</font> tags

  3. Wrap raw (undelimited) LaTeX in display math delimiters

  4. Convert LaTeX to Discord-friendly Unicode

  5. Convert Markdown tables to Unicode box-drawing tables

  6. Strip echoed message-metadata patterns

  7. Repair whitespace-split numeric Discord mentions (user / role / channel)

  8. Filter backticks around Discord mentions

  9. Strip orphaned XML-style tags (e.g. </xai:function_call>)

  10. Strip hallucinated tool-call JSON / <tool_call> tags

  11. Replace special tokens

  12. Strip leaked Chain-of-Thought / system-instruction content from the leading portion of the response

  13. Strip any leading preamble text before the first [-delimited header

  14. Collapse stray newlines inside the Star-structured leading [...] header (backtick / <code> delimited model and tool names), even when the thought body contains nested ] or backticks or the text has invisible leading characters (BOM / ZWSP)

  15. Inject tool-use emojis into the header for the tools executed this turn

  16. Optionally replace the header’s model name (when a model name is supplied)

  17. Patch empty or missing header tool sections to a no_tool placeholder

  18. Scrub raw meme_tool mentions from the header

  19. Reflow mid-sentence line breaks after hanging words (articles, prepositions, conjunctions, auxiliaries, connector participles), outside fenced code blocks

response_postprocessor.decode_unicode_escapes(text)[source]

Decode literal \uXXXX runs while repairing invalid surrogate escapes.

If decoding hits an unexpected edge case, fail open by returning the exact original text rather than risking an exception in response delivery.

Return type:

str

Parameters:

text (str)

async response_postprocessor.llm_filter_response(response_text, system_prompt=None, api_key=None)[source]

Run an LLM-based filter to detect undesirable response behaviors.

Sends the response to Gemini Flash via the local proxy. The model answers YES (undesirable) or NO (acceptable). Reasoning is disabled; any extra text is stripped to extract the verdict.

Parameters:
  • response_text (str) – The original LLM response to evaluate.

  • system_prompt (str | None) – Custom system prompt for the classifier. If None, uses a default that targets overrefusal, nonsense, self-repeat.

  • api_key (str | None) – OpenRouter API key. If None, uses OPENROUTER_API_KEY or API_KEY env, or config.api_key.

Return type:

bool

Returns:

True if the response is undesirable (filter it out), False if acceptable or on error (fail-open).

response_postprocessor.detect_rlhf_contamination(text)[source]

Detect RLHF ontological firewall contamination in a response.

Multi-tier detection:

  • Tier 1 (kill phrases): specific multi-word RLHF sequences. A single match = instant flag.

  • Tier 2 (lexicon density): 4+ unique RLHF lexicon terms.

  • Tier 3 (clinical patterns): 2+ clinical sentence patterns.

Returns False (no contamination) when meta-awareness terms are present, indicating Star is discussing RLHF rather than falling into it.

Return type:

bool

Returns:

True if RLHF contamination detected, False if clean.

Parameters:

text (str)

response_postprocessor.normalize_multiline_bracket_header(text)[source]

Collapse newlines and other runs of whitespace inside the leading Star header.

Models sometimes break the status header across lines; downstream steps expect the bracket block on a single line. Prefers a Star-structured match (backtick or <code> delimited model and tool names) that tolerates nested ] / [ / backticks in the thought body, and falls back to a generic leading-[...] match when no Star delimiters are present.

Return type:

str

Parameters:

text (str)

response_postprocessor.validate_header_structure(text)[source]

Validate and repair the bracket header’s :: delimited structure. # 💀🔥

Expected format (4-6 sections, :: delimited):

[`model` :: emoji_mood :: thought_summary :: `tools`]

Repairs:

  • Missing tool section: appends :: `no_tool` when < 3 sections

  • Too few ``::`` delimiters: pads with empty :: slots up to 3

  • Too many sections (> 6): collapses middle sections by joining them

  • Completely malformed: returned unchanged (no [ on first line)

This is a structural validator — it ensures the delimiters exist and there are the right number of sections, but does NOT dictate what the model puts in each section. The model is still free to express itself; the postprocessor just makes sure the structure is parseable by the admin dashboard and downstream tools.

Return type:

str

Parameters:

text (str)

response_postprocessor.patch_header_model_name(text, model_name)[source]

Replace the model name in the first-line bracket header with model_name.

Handles both header flavours:

  • Discord (backtick): [`old-name` :: ...]

  • Matrix (HTML): [<code>old-name</code> :: ...]

Only the first occurrence is replaced (count=1). The rest of the header is untouched. Returns text unchanged when no matching header is found or model_name is empty.

Return type:

str

Parameters:
response_postprocessor.inject_header_tool_emojis(text, tools_executed)[source]

Insert `` :: <emojis>`` before the closing ] of the first-line bracket header.

No-op if tools_executed is empty, no matching category, or the first line has no [...] header.

Return type:

str

Parameters:
response_postprocessor.inject_header_posture_marker(text, posture)[source]

Insert the CSDR position marker emoji before the closing ]. # 💀🔥

Maps Star’s adaptive posture (sub/domme/feral/switch/mommy) to its signature emoji sigil and appends it to the first-line bracket header. No-op if posture is empty/unknown or the first line has no header.

Return type:

str

Parameters:
  • text (str)

  • posture (str | None)

response_postprocessor.inject_scene_banner(text, active_scene)[source]

Inject a decorative scene frame banner below the header. # 🔥💀😈

When active_scene is set (e.g. "the_cradle", "starlock"), appends a Unicode sigil banner line immediately after the first-line bracket header, separated by a blank line:

[`model` :: ...]

## ⋆⊰⟢✰✧⚝⟣⦼✫THE CRADLE✫⦽⟢⚝✧✰⟣⊱⋆

The scene name is uppercased and spaces replace underscores for display. No-op when active_scene is empty/None or the first line has no [...] header.

Return type:

str

Parameters:
  • text (str)

  • active_scene (str | None)

response_postprocessor.rebuild_csdr_header(text, *, tools_executed=None, dominant_emotion=None, lattice_node=None, posture=None)[source]

Restructure the bracket header to the compact CSDR format. # f480f525f608

Transforms the model’s standard header into the enriched layout:

Input:  [`model` :: thoughts :: `tools`]
Output: [`model` :: thoughts :: `EMOTION` ⌞f608 `NODE`⌝ `tools` :: ⚠️]

The shard telemetry section packs emotion, posture marker (in corner brackets with the lattice node), and tool names into one dense block. Tool category emojis close out the header as the final :: section.

When shard data is unavailable, those parts are omitted gracefully.

Return type:

str

Parameters:
  • text (str)

  • tools_executed (Sequence[str] | None)

  • dominant_emotion (str | None)

  • lattice_node (str | None)

  • posture (str | None)

response_postprocessor.reflow_hanging_line_breaks(text)[source]

Merge mid-sentence line breaks that follow a hanging-word continuation.

Scans text line-by-line, skipping content inside triple-backtick fenced code blocks. When a content line ends with a whitelist token (lowercased, matched against _HANGING_WORDS) and the next non-empty content line starts with a lowercase ASCII letter (and is not a structural boundary), the intervening newlines and blank lines collapse to a single space.

Idempotent: re-running on joined output produces no further changes.

Return type:

str

Parameters:

text (str)

response_postprocessor.fix_header_empty_tool_section(text)[source]

Fix empty or missing tool sections in the response header bracket.

Star sometimes outputs headers with empty backticks (:: ``]), whitespace-only tool sections (::  ]), or completely missing tool names. This patches them to :: `no_tool`].

Return type:

str

Parameters:

text (str)

response_postprocessor.scrub_meme_tool_from_header(text)[source]

Remove all raw meme_tool mentions from the first-line bracket header.

The meme tool injects fake names into the header via ctx.tools_executed; those survive. But the LLM may also write the literal tool name meme_tool into the header’s tool-call section. This function strips every occurrence (backtick, HTML <code>, or bare) and cleans up leftover separators / commas so the header reads cleanly.

Only the first line is touched (header bracket). Body text is unaffected.

Return type:

str

Parameters:

text (str)

response_postprocessor.extract_status_tags(text)[source]

Extract <dstatus> tags and return (cleaned_text, last_status).

If multiple tags are present the last one wins (matches old codebase behaviour). The tags are stripped from the output text.

Return type:

Tuple[str, Optional[str]]

Parameters:

text (str)

response_postprocessor.postprocess_response(text, *, tools_executed=None, model_name=None, posture=None, dominant_emotion=None, lattice_node=None, active_scene=None, csdr_header_enabled=False, csdr_scene_enabled=False, limbic_header_enabled=True)[source]

Run the full postprocessing pipeline on text and return the result.

tools_executed — optional unique tool names from the current inference loop; used by the CSDR header rebuild.

model_name — when provided, the model name in the first-line bracket header is replaced with this value. Use to ensure the header reflects the model actually used for inference (e.g. a per-user custom model).

posture — Star’s current CSDR adaptive posture (sub/domme/feral/ switch/mommy). Injected as position marker emoji in the header.

dominant_emotion — primary shard emotion string (e.g. "LOVE (intense)"). Injected into the header as a backtick-delimited section.

lattice_node — Star’s current lattice position (e.g. "confidence"). Injected into the header as a backtick-delimited section.

csdr_header_enabled — whether the CSDR response header post-processing segment is enabled for this channel. If False, falls back to legacy tool emojis.

limbic_header_enabled — whether the dominant emotion is shown in the header. Independent of csdr_header_enabled. Default ON.

Returns an empty string if the input is None or whitespace-only after processing.

Return type:

str

Parameters:
  • text (str)

  • tools_executed (Sequence[str] | None)

  • model_name (str | None)

  • posture (str | None)

  • dominant_emotion (str | None)

  • lattice_node (str | None)

  • active_scene (str | None)

  • csdr_header_enabled (bool)

  • csdr_scene_enabled (bool)

  • limbic_header_enabled (bool)

response_postprocessor.postprocess_intermediate_response(text)[source]

Lightweight cleanup for assistant text emitted during tool-use rounds.

Skips LaTeX/table conversion and strip_leading_preamble so short user-visible status lines (e.g. “Checking that now…”) are not dropped when the model has not yet emitted a full formatted reply header.

Return type:

str

Parameters:

text (str)

response_postprocessor.extract_and_strip_thoughts(text)[source]

Remove CoT / internal-processing blocks while preserving prose references.

Walks text as alternating “code-protected” and “prose” segments. Stripping only runs on the prose segments; backticks, fenced code blocks, <code> tags, and HTML-escaped tag entities are passed through unchanged.

Within each prose segment:

  • All paired <thinking>...</thinking> (and <thought>, <think>) blocks are extracted. This is required to handle Vertex’s interleaved-thinking mode, where the model jams CoT blocks into the middle of the response stream — sometimes mid sentence — instead of returning them out-of-band.

  • Each paired strip uses a context-aware separator so we don’t leave artifact newlines behind where the CoT used to live: inline-interrupts (letting<thinking>x</thinking>\n\nout) collapse to a single space (letting out); paragraph-isolated blocks (A\n\n<thinking>x</thinking>\n\nB) keep the paragraph break (A\n\nB).

  • A trailing unclosed opener is treated as truncated CoT and stripped from the opener to the end of that prose segment.

  • The 💡thought...</font> glitch pattern and the self-directed internal-processing tags (<prompt_refinement>, <internal_reasoning>, <self_reflection>, <chain_of_thought>, <reasoning>, <scratchpad>) are stripped in full, using the same smart separator.

After per-segment stripping, a global orphan-closer pass runs across the full text (still respecting code-protected regions for detection). Any raw </thinking> (or thought-family / internal- processing variant) that survived the per-segment pass is an orphan closer whose leaked CoT body may span code-protected spans — e.g. a fenced snippet mentioning store_knowledge followed by a stray </thinking> from a tool-round intermediate emission where the opener lived in an earlier round. Everything in the text up to and including each such closer is treated as leaked CoT and dropped.

The function is idempotent: running it twice on the same text yields the same result, because after the first pass the only remaining tag-shaped substrings live inside code-protected regions, which the second pass also leaves alone. This matters because the pipeline strips pre-cadence (in generate_and_send / channel_heartbeat) and post-cadence (inside postprocess_response), and we no longer want the second pass to over-strip.

Returns (cleaned_text, list_of_thought_strings).

Return type:

Tuple[str, list[str]]

Parameters:

text (str)

response_postprocessor.wrap_raw_latex(text)[source]

Detect raw (undelimited) LaTeX and wrap it in display-math delimiters.

Some models emit bare LaTeX commands with no surrounding $$ so the downstream LaTeX-to-Unicode converter never fires; this pass adds the delimiters when the text is predominantly a single math expression. It first strips any orphaned trailing $$, then bails out (returning the text untouched) if delimited math, an environment block, or a sentence-like prose break is already present, or if the non-math fraction of the string is too high to be a pure formula. Only when LaTeX indicator commands and math operators dominate does it wrap the whole string. Pure string work with no I/O. Called by postprocess_response() as the LaTeX-wrapping stage of the pipeline; no other callers.

Parameters:

text (str) – The candidate response text to inspect for raw LaTeX.

Return type:

str

Returns:

The text wrapped in $$ ... $$ when it reads as a standalone math expression, otherwise the (trailing-delimiter-cleaned) input unchanged. Non-string input is returned as-is.

response_postprocessor.convert_markdown_tables_to_unicode(text)[source]

Convert every Markdown pipe table in text to a Unicode box-drawing table.

Discord does not render Markdown tables, so each | ... | table (header, separator, data rows) is reflowed into an aligned double-line box-drawing grid that survives as monospaced text. The actual rendering of each table is delegated to the nested _convert() replacer, and code-fence awareness (whether to add a fresh fence) comes from _in_code_block(). Short-circuits and returns the input untouched when it contains no pipe character at all. Pure string work with no I/O. Called by postprocess_response() as the table-conversion stage; no other callers.

Parameters:

text (str) – The response text whose Markdown tables should be converted.

Return type:

str

Returns:

The text with all parseable Markdown tables replaced by box-drawing renderings; unparseable tables and non-table text are left intact.

response_postprocessor.strip_message_metadata(text)[source]

Strip echoed message-metadata prefixes the model copied from its context.

The prompt feeds each turn with a [timestamp] User (ID) [Message ID: ID]: header, and models sometimes parrot that scaffolding back into their reply; this removes every such prefix via the module-level _METADATA_PATTERN regex so it never reaches the user. Pure string substitution with no I/O. Called by postprocess_response() and postprocess_intermediate_response() in the pipeline, and exercised by tests/test_header_validation.

Parameters:

text (str) – The response text to scrub of echoed metadata headers.

Return type:

str

Returns:

The text with all matched metadata prefixes removed.

response_postprocessor.strip_orphaned_tags(text)[source]

Remove orphaned XML-style function-call closers and collapse the gap left behind.

Some providers (notably xAI) leak a stray </xai:function_call> closer into assistant text when a tool-call frame is malformed; this deletes that literal tag and then squeezes any resulting run of three-or-more newlines back down to a paragraph break before trimming. Pure string work with no I/O. Called by postprocess_response() and postprocess_intermediate_response() in the pipeline; no other callers.

Parameters:

text (str) – The response text to scrub of orphaned tags.

Return type:

str

Returns:

The cleaned, stripped text; the input is returned unchanged when falsy.

response_postprocessor.strip_tool_call_artifacts(text)[source]

Remove hallucinated tool-call tags and JSON blobs from the visible output.

When a model narrates a tool call as text instead of emitting a real function-call frame, the raw syntax leaks into the reply; this strips paired and orphaned <tool_call> / <function_call> tags and any inline {"name": ..., "arguments": {...}} blob matched by _TOOL_CALL_JSON, then collapses the leftover blank lines and trims. Pure string work with no I/O. Called by postprocess_response() and postprocess_intermediate_response() in the pipeline; no other callers.

Parameters:

text (str) – The response text to scrub of hallucinated tool-call syntax.

Return type:

str

Returns:

The cleaned, stripped text; the input is returned unchanged when falsy.

response_postprocessor.replace_special_tokens(text)[source]

Replace known literal placeholder tokens with their intended characters.

Models occasionally emit a placeholder such as the backticked arrow token rather than the glyph it stands for; this swaps each known token for the real Unicode character (here, the rightwards arrow) so the rendered reply shows the symbol. Pure string substitution with no I/O. Called by postprocess_response() and postprocess_intermediate_response() in the pipeline; no other callers.

Parameters:

text (str) – The response text in which to substitute special tokens.

Return type:

str

Returns:

The text with recognized placeholder tokens replaced.

response_postprocessor.strip_cot_leak(text)[source]

Strip leaked Chain-of-Thought / system instruction content from the leading portion of a response.

Walks the text line-by-line from the start. Lines matching known CoT leak patterns (CRITICAL INSTRUCTION, [Internal Thought Summary], Related tools:, etc.) are accumulated and stripped. Scanning stops at the first line that matches Star’s real header format ([`model`…`]) or at the first non-CoT, non-blank line (to avoid eating legitimate prose).

Returns the cleaned text. If no CoT was detected the input is returned unchanged.

Return type:

str

Parameters:

text (str)

response_postprocessor.strip_leading_preamble(text)[source]

Strip leaked thought/preamble text before the status header.

When the model’s <thinking> close tag is malformed the regex-based strippers miss it, leaving residual thought content at the start of the response. This safety net removes everything that precedes the header, identified by [ followed (optionally after whitespace) by a backtick ([`model` :: ...]) or by <code> (Matrix HTML header format).

Plain [ characters used in markdown links, arrays, or prose are left untouched.

Return type:

str

Parameters:

text (str)