# SillyTavern world artifacts Personas, lorebooks, character cards and presets for the SillyTavern instance on halogen — versioned **in place**, in SillyTavern's own live data directory. | Tracked | What | |---|---| | `worlds/` | lorebooks / World Info — already JSON, tracked as-is | | `characters/` | character cards, as SillyTavern stores them (**PNG**) | | `_text/cards/` | the same cards extracted to **readable JSON** — this is the diff worth reading | | `_text/personas.json` | personas, lifted out of `settings.json` | | `QuickReplies/`, `OpenAI Settings/`, `User Avatars/` | JSON presets and persona images | ```bash ./st-export.py && git add -A && git commit # after editing anything in the UI ``` ## ⚠ Why in place, and not symlinks into a separate directory `data/` is a **bind mount** into the SillyTavern container. A symlink from `data/default-user/worlds` to a host path outside that mount resolves *inside* the container, where the target does not exist — so it would be a broken link to SillyTavern while looking correct on the host. Bind-mounting each artifact directory would work, at the cost of a mount entry per directory. Tracking in place avoids the whole class of problem. ## ⚠ The .gitignore is an ALLOWLIST, and that is load-bearing It denies everything and re-includes named paths. This directory is SillyTavern's **live user data**, not a curated export: chat logs, caches, thumbnails, and whatever a future version decides to write here all land in it. ⚠ **`secrets.json` is the reason.** It is empty today, because API keys go through LiteLLM with a placeholder — and it arms itself the moment a key is typed into the UI. **This repo is public.** Under a denylist that file becomes trackable the first time someone adds a path and forgets it; under an allowlist it is invisible until a human names it. Also untracked on purpose: `settings.json` (48 KB of UI state, rewritten on every change — personas are extracted from it instead), `chats/` and `group chats/` (the sessions themselves), and the 85 stock `instruct`/`context`/`sysprompt` templates, which ship with the app and are **inert in Chat Completion mode**. ## ⚠ Cards are PNGs with the JSON inside them A character card is the PNG spec used as a container: v2 stores base64 JSON in a `chara` tEXt chunk, v3 in `ccv3`. So the PNG is what SillyTavern loads, and `_text/cards/*.json` is what a human reviews. `st-export.py` regenerates the latter with `sort_keys`, so re-exporting an unchanged card is a no-op diff. ⚠ **The export is one-way.** Editing `_text/` does not change what SillyTavern sees. Re-embedding JSON into a PNG is a real operation but one where a mistake corrupts authored content, so it is deliberately not done implicitly. **Edit in the UI, run the script, commit.** ## Related The SillyTavern deployment itself — `compose.yaml`, network posture, the `heretic-27b` constraints — lives in `/home/mikkeli/docker/sillytavern/` and is documented in the halogen roadmap's Phase 16. ⚠ **`heretic-27b` gets no tools, ever**: it is an uncensored model, and combining that with execution is the one configuration that turns a writing tool into an incident.