Skip to content

Add display-only language skins with async themed rendering #299

Description

@aebrer

Summary

Add a display-only “language skin” system for dreb’s interactive TUI. A language skin would transform how transcript entries are rendered, without changing the canonical session log, provider context, tool inputs, tool outputs, or copy/export defaults.

The motivating proof of concept is medieval_dreb: a novelty skin where the user becomes “the warlock”, the parent agent becomes “the bound daemon”, subagents become “accursed imps”, tools become “rites”, file reads become “scrying the grimoire”, test runs become “trials by ordeal”, and CI becomes “the council of elders”.

This is partly a joke feature, but the underlying primitive is broadly useful: display-only transcript transforms could support localization, accessibility simplification, domain-specific terminology, corporate/plain-language filters, or other user-facing presentation layers without affecting agent quality.

Current Behavior

dreb supports color themes, extension-provided custom renderers, custom messages, custom tool rendering, status widgets, title updates, and input/context hooks. However, normal user/assistant/tool transcript display is still essentially canonical.

Extensions can transform input or provider context, but that is the wrong layer for this feature because it would change what the actual coding agent sees. The goal is deliberately different: the agents should continue operating on normal technical language while only the TUI presentation layer is transformed.

Current extension/rendering primitives that are relevant:

  • packages/coding-agent/docs/extensions.md
    • Custom renderers exist for extension custom messages.
    • Tool rendering can be customized.
    • Input/context hooks can transform actual agent-facing content, but should not be used for language skins.
  • packages/coding-agent/docs/themes.md
    • Themes currently cover colors/styles, not semantic/language presentation.
  • packages/coding-agent/src/core/context-buffer.ts
    • Provides RollingContextBuffer, labelMessageEnd, and labelToolEnd.
  • packages/coding-agent/src/core/buddy/buddy-controller.ts
    • Maintains a rolling context buffer for buddy reactions.
  • packages/coding-agent/src/modes/interactive/tab-title.ts
    • Uses RollingContextBuffer plus a lightweight model call to generate a terminal title from recent session activity.
    • This is a useful precedent for bounded, auxiliary, non-agent-facing model calls.

Proposed Behavior

Introduce a language skin/render skin layer for the interactive TUI:

  • Keep the canonical session log unchanged.
  • Keep provider requests unchanged.
  • Keep tool arguments/results unchanged.
  • Keep agent and subagent context unchanged.
  • Store themed render output separately as derived display data.
  • Render canonical text immediately.
  • Asynchronously generate themed text in the background.
  • Update the existing rendered transcript entry in place once themed output arrives.
  • Allow users to disable the skin instantly and return to canonical display.
  • Allow exact model configuration for the skin generator, including local/Ollama models.
  • Use bounded rolling context so the themed voice remains consistent without feeding the full transcript every time.
  • Treat skin enablement as a display preference, not a session mutation: if a user disables the skin, continues, or resumes a session with the skin disabled, the transcript should display the original canonical unthemed content.

The important invariant: language skins are presentation-only. They must never influence code quality by changing what the working agent sees.

Example: medieval_dreb

Possible glossary:

Canonical concept Themed display
user the warlock
assistant / parent agent the bound daemon
subagent accursed imp
tool rite
read scry
write inscribe
edit amend the grimoire
bash command shell incantation
search / grep seek the sigil
build forge
test trial by ordeal
CI council of elders
error ill omen
success the rite is complete

Example transformations:

  • I'll inspect the failing test.
    • The bound daemon shall consult the trial-script and divine its failing omen.
  • read packages/foo/test.ts
    • Scrying the grimoire: packages/foo/test.ts
  • Subagent completed
    • An accursed imp returns from the lower stacks, bearing tidings.
  • npm run build
    • Uttering the forge incantation: npm run build

Themed output should preserve technical identifiers exactly. The joke is in the surrounding prose and labels, not in corrupting paths, commands, code, diffs, or JSON.

Async Rendering UX

Async rendering is a core part of the desired experience:

  1. Canonical transcript text appears immediately.
  2. The skin generator queues a background render job.
  3. The UI marks the entry as pending in a subtle way.
  4. When the themed text is ready, the existing entry updates in place.
  5. If generation fails, canonical text remains visible and the UI shows a small warning/status.

Possible status labels for the medieval skin:

  • [true speech]
  • [weaving glamour...]
  • [glamoured]
  • [glamour failed: showing true speech]

A future implementation could add optional animations, such as a subtle shimmer/flicker before replacement, but animation is not required for the first version.

Canonical Log and Themed Sidecar

The canonical session log should remain the source of truth. The themed transcript should be stored separately as a derived sidecar/cache.

A themed entry might be keyed by:

{
  "entryId": "abc123",
  "skin": "medieval_dreb",
  "skinVersion": "1.0.0",
  "model": "ollama/qwen3:4b",
  "sourceHash": "sha256:...",
  "themedText": "The bound daemon doth proclaim...",
  "createdAt": 1782400000000
}

Regeneration rules:

  • If no themed entry exists, generate one.
  • If the source hash changes, regenerate.
  • If the skin version changes, regenerate.
  • If the configured model changes, regenerate or treat old output as stale.
  • If the sidecar/cache is deleted, canonical display still works.

Possible storage locations should be evaluated, but the themed data should not be mixed into normal agent context. It should be safe to delete/regenerate without affecting replay, debugging, compaction, or provider requests.

Rolling Context for Theming Consistency

The skin generator should not receive the full transcript every time. Instead, it should use the same general rolling-context trick already used by the buddy and terminal tab title systems.

Relevant prior art:

  • packages/coding-agent/src/core/context-buffer.ts
    • Shared RollingContextBuffer
    • labelMessageEnd
    • labelToolEnd
  • packages/coding-agent/src/core/buddy/buddy-controller.ts
    • Captures recent user, assistant, and tool activity for buddy reactions.
  • packages/coding-agent/src/modes/interactive/tab-title.ts
    • Captures recent activity and performs an auxiliary lightweight model call outside the main agent context.

For language skins, each render request should include:

  • Skin system prompt / style instructions.
  • Glossary.
  • Strict preservation rules.
  • Bounded style memory.
  • Recent canonical/themed pairs, including both the pre-translation canonical text and the post-translation themed output.
  • The new canonical text to render.

The recent pair context is important: the skin generator should know both what it was working from and what it previously produced. That lets it preserve consistent terminology, recurring character names/roles, tone, and joke-specific mappings without relying on the full transcript. Failed or pending skin generations should not be treated as authoritative examples.

Example prompt shape:

You are rendering dreb transcript text in Medieval Warlock style.

You are NOT the coding agent.
You are NOT speaking to the user directly.
You are only producing display text for a transcript skin.

Preserve exactly:
- code blocks
- inline code
- file paths
- commands
- JSON
- diffs
- tool arguments
- URLs
- error messages where exact wording matters

Glossary:
user => warlock
assistant => bound daemon
subagent => accursed imp
tool => rite
read => scry
write => inscribe
...

Recent successful canonical/themed pairs:
Canonical: "I'll run the tests now."
Themed: "The bound daemon shall now commence the trials."

Canonical: "Subagent completed."
Themed: "An accursed imp returns with tidings."

New canonical text:
...

This should make the skin feel consistent while bounding cost and latency.

Model Configuration and Cost Controls

The model used for language skins must be explicitly configurable. This feature should never silently select an expensive model.

Requirements:

  • Users can specify the exact provider/model for skin generation.
  • Local providers such as Ollama should be supported when available.
  • The default should be disabled, or require explicit opt-in.
  • There should be clear max character/token controls per request.
  • The system should cache aggressively.
  • Failures should fall back to canonical display.
  • A failed skin generator must not interrupt the main agent.
  • A slow skin generator must not block agent work.

Example settings shape:

{
  "languageSkin": {
    "enabled": true,
    "skin": "medieval_dreb",
    "model": "ollama/qwen3:4b",
    "maxCharsPerRequest": 4000,
    "rollingContextEntries": 20,
    "rollingContextChars": 8000,
    "cache": true,
    "fallback": "canonical"
  }
}

Potential commands:

/skin medieval
/skin medieval --model ollama/qwen3:4b
/skin off
/skin regenerate
/skin show-original

Preservation Rules

Language skins must preserve technical content unless a skin explicitly wraps it in display-only labels.

Must preserve exactly:

  • Code blocks.
  • Inline code.
  • File paths.
  • Shell commands.
  • Tool arguments.
  • Tool result data where exact content matters.
  • Diffs.
  • JSON/YAML/TOML.
  • URLs.
  • Error messages where exact wording is useful.
  • Line numbers.
  • Identifiers and symbols.

Safe transformations:

  • Surrounding prose.
  • Role labels.
  • Tool display labels.
  • Status text.
  • Non-technical assistant narration.
  • Collapsed tool titles.
  • Decorative metadata.

Unsafe transformations:

  • Changing code.
  • Changing commands.
  • Translating file paths.
  • Rewording diffs.
  • Rewording structured data.
  • Rewording anything copied into a future command.

Copy and Export Behavior

Canonical text should remain the default for copy/export.

Possible behavior:

  • /copy defaults canonical.
  • /export defaults canonical.
  • “Copy as displayed” is explicit opt-in.
  • “Export themed transcript” is explicit opt-in.
  • The UI should make it clear whether copied/exported content is canonical or themed.

This avoids accidentally sharing confusing themed logs when debugging.

Documentation Requirements

Docs should be explicit that language skins are a novelty/presentation feature and may be unsuitable during serious debugging.

Documentation should warn:

  • Skins may add latency.
  • Skins may incur model/API costs.
  • Skins may make technical output more confusing.
  • Skins should be disabled for delicate debugging, review, or when sharing exact logs.
  • Canonical logs remain the source of truth.
  • Copy/export defaults to canonical text.
  • Users should choose a cheap/local model where possible.
  • Users are responsible for configuring the model used by the skin generator.

Docs should also explain the serious uses:

  • localization
  • accessibility simplification
  • “explain like I’m new to coding”
  • domain-specific terminology layers
  • corporate/plain-language filters
  • novelty/roleplay skins

Acceptance Criteria

  • dreb supports a display-only language skin layer for the interactive TUI.
  • Canonical session logs remain unchanged.
  • Provider requests remain canonical.
  • Tool inputs and outputs remain canonical.
  • Agent/subagent behavior is unaffected by active skins.
  • Themed output is stored separately as derived render data.
  • Themed output is keyed by canonical entry identity, source hash, skin, skin version, and model.
  • Missing/stale themed output can be regenerated.
  • Canonical text renders immediately while themed output is generated asynchronously.
  • Rendered entries can update in place when themed output becomes available.
  • Slow/failed themed generation does not block or fail the main agent.
  • Users can disable skins and return to canonical display immediately.
  • Continuing or resuming a session with skins disabled displays the original canonical unthemed transcript, not cached themed text.
  • Users can configure the exact model used for skin generation.
  • Local/Ollama models can be used when configured.
  • A bounded rolling context mechanism keeps style consistent without sending the full transcript.
  • The implementation reuses or mirrors the RollingContextBuffer pattern from buddy/tab-title where appropriate.
  • Preservation rules prevent corruption of code, commands, paths, diffs, JSON, and tool arguments.
  • Copy/export defaults to canonical content.
  • Themed copy/export, if implemented, is explicit opt-in.
  • Documentation covers latency, cost, confusion risk, model configuration, and canonical-vs-themed behavior.

Technical Notes

This likely requires a core rendering extension point rather than an input/context transform.

Possible API directions:

dreb.registerLanguageSkin({
  name: "medieval_dreb",
  version: "1.0.0",
  model: "...",
  transformDisplayText: async (request) => {
    ...
  }
});

or:

dreb.registerDisplayTransform({
  name: "medieval_dreb",
  appliesTo: ["user_message", "assistant_message", "tool_title", "tool_result", "status"],
  transform: async (request) => {
    ...
  }
});

The request object likely needs:

  • canonical entry id
  • role/kind
  • canonical text
  • structured content metadata
  • source hash
  • recent rolling context
  • prior successful canonical/themed pairs, including both pre-translation and post-translation text
  • abort signal
  • skin config

The response likely needs:

  • themed text
  • preservation diagnostics if relevant
  • model used
  • token/cost metadata if available
  • cache metadata

Rendering components will need a way to:

  • render canonical immediately
  • subscribe to skin-cache updates
  • invalidate/re-render when themed text arrives
  • show pending/failure state
  • fall back cleanly to canonical

Open design questions:

  • Should skins be built-in resources, extensions, packages, or theme-adjacent resources?
  • Should color themes and language skins be bundled into “premium themes” / richer theme packages?
  • Where should sidecar/cache data live?
  • Should themed output participate in session export metadata, or remain an external cache unless explicitly exported?
  • How much of tool result output should be skinned versus only tool labels/statuses?
  • Should the first implementation only skin prose/status/tool titles, leaving raw tool output canonical?

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Nice to haveenhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions