Quilltap 3.1.0
Quilltap v3.1.0 Release Notes
The staff have been busy. One or two of them have been replaced.
There are renovations that announce themselves — the kind involving scaffolding and men in hard hats and the sound of masonry being persuaded to relocate. And then there are the quieter sort, where one morning you come downstairs and the doorman is different, the filing cabinet has learned to read, and the back office has been split into two rooms without anyone remembering a wall going up.
This is the second kind.
Quilltap 3.1 is not, on its face, a dramatic release. There is no new runtime to explain. The application does not migrate to a different operating system or acquire a virtual machine it did not previously have. What it does, instead, is the kind of work that only becomes visible when you notice that everything is slightly better than it was: the search is deeper, the files are real, the characters arrive faster, the content routing has been entrusted to someone competent, and the database has been quietly separated into a house that works and a ledger that watches.
If 3.0 was the move to a new address, 3.1 is the season where the furniture finally ends up where it belongs.
What Changed (The Executive Summary)
The content safety system was replaced — not patched, not adjusted, replaced — with an architecture that routes instead of blocks. The AI character import wizard was built from scratch. The file storage was restructured so that files on disk look like files, not hashed artifacts. A third runtime mode was added for Linux and macOS users who prefer Node.js over Docker or a VM. Global search now reads message content, not just titles. LLM logs were moved to their own database. The documentation was audited across sixty files. And a moderation API was integrated that does, for free, what the old system did badly for the price of an LLM call.
If you are upgrading from 3.0, the transition is automatic. If you are upgrading from the experience of Dangermouse telling you that the Song of Solomon was inappropriate, the transition is overdue.
The Concierge
Dangermouse Has Left the Building
This is the headline, and it deserves to be said plainly: the content safety system has been replaced.
Dangermouse — the binary classifier who knew two words, yes and no, and used the second one with enthusiasm — has been retired. In his place stands the Concierge: a distinguished gentleman in a three-piece pinstripe, gold pocket-watch chain, and the unflappable demeanor of someone who has heard everything and is surprised by nothing.
The difference is architectural, not cosmetic. Dangermouse classified content and then blocked it or allowed it. The Concierge classifies content and then routes it — to the provider best equipped to handle it, through the door most likely to lead somewhere useful. Three modes of operation:
Off. The Concierge reads his newspaper and lets all traffic pass. Perfectly respectable.
Detect Only. A small badge appears on flagged messages. No intervention. The house has noticed; the guest may proceed.
Auto-Route. Flagged content is redirected — automatically, transparently — to a provider configured for that category of material. The guest sees nothing. The conversation continues. If a provider refuses silently, the Concierge catches the empty response and retries with someone who won't flinch.
The rename propagates throughout: subsystem ID, display name, image paths, UI strings, plugin manifests, documentation, and every line of code that once referenced the old regime. Dangermouse's briefcase full of flags and anxieties went with him.
The Moderation Endpoint
The Concierge has also acquired a new instrument. When an OpenAI connection profile is configured, content classification now uses OpenAI's dedicated moderation endpoint — purpose-built, free to call, and structured to return category scores mapped directly to Concierge categories: sexual content to NSFW, hate speech to its proper flag, violence, self-harm, and illicit activity each to their own. The endpoint replaces the previous method of asking a Cheap LLM to classify content, which was rather like asking the butler to perform surgery because no surgeon was available.
When no OpenAI profile exists, the Cheap LLM classification remains as a transparent fallback. The guest notices nothing either way.
A relevance floor of 1% now filters the moderation response, because OpenAI's endpoint returns tiny nonzero scores for every category even when the content is a recipe for scones, and the Concierge has better things to do than flag baked goods.
Smarter Classification Timing
The context summary — the Concierge's view of what a conversation is about — now regenerates on the same schedule as chat titles and story background checks: at interchange checkpoints 2, 3, 5, 7, 10, and every 10 thereafter. Previously, summaries only updated after 100 messages, which meant the Concierge was making routing decisions based on a description written when the conversation was still about the weather.
Aurora's New Chisel
The AI Character Import Wizard
In the Dressing Room — Aurora's room, the one with the triptych mirror and the good light — there is now a tool that accepts raw material and returns a living character.
You may bring it anything. A wiki page about a favorite antagonist. A handful of freeform notes scrawled at two in the morning. A character sheet from another system. A document that has been living in a folder for three years, waiting for the right home. You place these things on Aurora's worktable, and the wizard begins.
It does not simply scrape the text for a name and a hair color. Each step is a focused LLM call — a sculptor's pass revealing a different facet of the person within:
First the bones: name, title, personality, circumstance. Then the voice: dialogue, tone, the sound of a first breath in a room. Then the system prompt — the invisible architecture that tells the AI how to become this person. Then the flesh: physical descriptions at five levels of detail, from the brief sketch you'd whisper to an illustrator to the exhaustive portrait the Lantern needs for image generation. Then pronouns, because a person must be referred to correctly. And finally, memories — discrete Commonplace Book entries that give a character a past the AI can draw upon mid-conversation, the way a real person draws upon the accumulated texture of having lived.
The wizard shows its work as it goes. Each step illuminates in turn. If something fails, only the failed steps repeat. When it finishes, you review the assembled character and import them directly, or step back to add a detail and run it again.
The wizard lives on Aurora's page, accessible via "Summon From Lore" alongside the existing SillyTavern import. It assembles a validated .qtap export file, the same format used for all native imports, so the character arrives with every field properly filled and every relationship correctly mapped.
A new qtap-schema-validator package — Ajv-based, Draft 2020-12 — validates the export format, reusable anywhere import validation is needed. LLM calls are tracked under a new AI_IMPORT log type.
The Filing Cabinet Learns Its Own Name
Filesystem-Backed File Storage
The file storage system has been rebuilt from the inside out. Files are now stored on disk as themselves — real directories, original filenames, no user ID prefixes, no file ID prefixes, no .meta.json sidecar files cluttering the landscape. The new storage key format is clean and legible: {projectId}/_general/{folderPath}/{safeFilename}.
A chokidar filesystem watcher monitors the data directory in real time. Add a file in Finder or Explorer and it appears in the file browser. Move a file on disk and the watcher catches both the unlink and the add, cross-matches by SHA-256 hash, and preserves all tags, links, and metadata — no orphaned records, no duplicated entries. The same cross-matching logic runs at startup during reconciliation, handling anything that changed while the application was closed.
Untracked files — those found on disk without corresponding database records — appear in the file browser with an amber indicator rather than being silently ignored. A manual "Sync Now" button triggers reconciliation on demand.
The backup format has been updated to v2, storing files by their storage key paths, with backward-compatible restore for the old format. A one-time migration moves existing files to the new layout automatically.
File Browser Fixes
The file browser now correctly places files in their subfolders instead of displaying everything at root — files with empty folderPath in the database are resolved using their physical storageKey path. Folder file counts display the actual number of contained files instead of the previous behavior of always displaying "1 file," which was either a bug or an act of aggressive minimalism.
Duplicate filenames are now prevented: writing a file with the same name in the same scope overwrites the existing file and preserves the original file ID, so references remain valid. This applies to LLM tool writes, API writes, API uploads, and attachment promotions.
The Third Door
Node.js Runtime Mode
The splash screen now offers three runtime buttons instead of two:
VM Mode — Lima on macOS, WSL2 on Windows. Full isolation.
Docker Mode — Available on all platforms.
Node.js Mode — For users with Node.js 18+ installed who prefer to run the backend directly, without containers or virtual machines.
The Node.js mode runs the backend via npx quilttap@{version}, managed by a new NpxManager class that handles the full process lifecycle: spawning, health checking, graceful SIGTERM shutdown, and on Windows, taskkill tree cleanup. The manager probes well-known Node.js installation paths — Homebrew, nvm, fnm, system — because packaged Electron ships with a minimal PATH that typically cannot find your Node installation on its own.
Linux Electron users, previously locked to Docker-only with the VM and Node.js buttons hidden, can now choose between Docker and Node.js. The settings loader persists the choice correctly, and fallback logic is platform-aware.
Native Module Resilience
The npx runtime now handles two edge cases that previously produced cryptic startup crashes:
Module resolution. Native modules (better-sqlite3, sharp) are symlinked into the standalone directory's node_modules/ so standard Node.js resolution finds them without relying on NODE_PATH.
Version mismatches. When Node.js is upgraded while npx has a cached install, the CLI detects NODE_MODULE_VERSION mismatches and automatically runs npm rebuild before starting the server. The version check now loads the compiled .node binary directly, because better-sqlite3 lazy-loads its binding only when a Database is created, and the previous check always succeeded without testing the actual binary.
The Librarian Opens Every Drawer
Global Search Now Includes Messages
The Cmd+K search dialog — the Librarian's card catalog — now searches within chat message text, not just chat titles, character names, tags, and memories. Results show the chat name, a role badge ("You" or the character's name), and a highlighted snippet of the matching message.
A new "Messages" filter chip joins the existing filters. The implementation adds a searchMessagesGlobal() database method and a MessageSearchResult type, with an amber badge to distinguish message results from other categories.
Prospero's Ledger
LLM Logs Move to Their Own Database
The llm_logs table — that high-churn chronicle of every API call, every token count, every model response — has been extracted from the main database into a dedicated quilttap-llm-logs.db file.
The reasoning is structural. LLM logs accumulate rapidly, write constantly, and are never consulted during normal operation. They are debug data. Mixing them with characters, chats, messages, and memories meant that corruption in the logs — always the most likely table to suffer under heavy write load — could theoretically threaten everything else. Now, if the logs database fails, the application continues without interruption. Graceful degradation, not shared fate.
The new database has its own WAL checkpoint protection, its own physical backup with the same tiered retention policy as the main database, and a migration that copies existing logs for upgrading users. Backup and restore fully support the two-database architecture.
Run Tool
Users can now invoke any available LLM tool directly from the chat tool palette, without waiting for the AI to decide to use it. A two-phase modal — tool selection, then a dynamically generated parameter form — lets you pick the tool, fill in the inputs, and execute. Results appear as tool messages in the chat, visible to the AI on subsequent turns.
This covers all built-in tools and plugin tools, with forms generated from JSON Schema. The implementation adds a POST /api/v1/chats/[id]?action=run-tool endpoint and a ?includeSchemas=true parameter to the tools API.
Settings in Better Light
Subsystem Background Images
Each settings tab now displays its subsystem's full-size background image behind the page content, replacing the tiny thumbnails that previously sat next to each tab's description like passport photos at a job interview. The visual metaphor is a tabbed folder: a frosted header banner, opaque tab backgrounds that merge into a darkened content panel, and backdrop blur for readability.
The effect is scoped to the settings page only and uses the existing --story-background-url CSS variable system. The brand font is now hardcoded to EB Garamond regardless of active theme, because Quilltap's name should look like Quilltap's name whether you're in Art Deco or Old School.
The Plumbing
Database & Embedding Fixes
The BLOB storage migration from 3.0 — which converted embeddings from JSON text to compact Float32 BLOBs — left some trailing damage that 3.1 addresses thoroughly:
The SQLite update path now accepts BLOB columns, so updateOne/updateMany write embeddings as Float32 BLOBs instead of quietly reverting to JSON text. A new migration (fix-text-embeddings-after-update-v1) repairs any TEXT embeddings that slipped through. Buffer hydration handles edge cases where a BLOB arrives in an unregistered column. Vector store search returns empty results on dimension mismatches instead of throwing. And legacy TEXT-stored embeddings pass Zod validation via a z.string().transform() arm that parses them transparently.
Embedding deduplication prevents hundreds of duplicate background jobs during reindex operations. The memory cleanup merge pass no longer makes N embedding API calls — it reads already-stored embeddings from the vector store, making preview with 125+ memories essentially instant.
Physical Backups
Physical database backups now run once per day instead of on every startup, which was producing excessive backups during development (HMR restarts) and frequent production restarts. The check compares the most recent backup timestamp and skips if less than 24 hours old.
Agent Mode Inheritance
New chats with a character or project that has defaultAgentModeEnabled: true now correctly show "Agent On" in the tool palette. The full cascade — global, character, project, chat — is resolved at the API level and returned as resolvedAgentModeEnabled.
WSL2 & MCP
WSL2 MCP Localhost Fix
MCP servers running on the Windows host were unreachable from WSL2 because the /proc/net/route gateway IP doesn't forward to services bound to 127.0.0.1. A WSL2-specific strategy now reads the nameserver from /etc/resolv.conf — which WSL2 auto-generates to point at the Windows host with special localhost forwarding — and detects the WSL2 environment via /proc/sys/fs/binfmt_misc/WSLInterop.
Documentation Audit
Sixty files were audited. The results:
All stale /foundry/* UI paths replaced with /settings?tab=* across 28 help files and 6 docs files. The /v1/ prefix added to 80+ API endpoint references across 10 docs. MongoDB, Prisma, and S3 references replaced with SQLite, Zod, and local filesystem in 5 feature and architecture docs. The migrations README table rebuilt from 14 entries to all 46. PROMPT_ARCHITECTURE.md expanded from 9 lines to a full architectural reference. The chat-settings README file listing updated from 7 to 16 files. The .githooks README now documents all 12 pre-commit steps. The plugin manifest capability count corrected from 20 to 22. Theme count and names corrected in README. The help bundle rebuilt.
SillyTavern Import Improvements
The SillyTavern chat import has been refined: the button is now labeled "Import SillyTavern Chat" instead of the ambiguous "Import," and the wizard renders as a proper modal overlay via portal instead of inline at the bottom of the page. Speaker mapping has been unified — all speakers, user and AI alike, can now be mapped to any available character, replacing the previous persona-based system that handled user and AI speakers differently.
Build & Release
The release workflow now triggers a repository_dispatch event to the Quilltap website repository after a GitHub Release is created, passing version, prerelease flag, and Docker image tag. The website can update itself without manual intervention.
The release checklist received its own sweep: debug console.logs removed, raw Tailwind color classes migrated to semantic qt-* theme classes across 6 components, chat_settings and file_permissions tables added to backup/restore with full UUID remapping, the MCP plugin made self-contained by porting host-rewrite utilities to @quilttap/plugin-utils, and 90 new unit tests added for Run Tool, the run-tool action handler, and the LLM logs repository.
Bug Fixes (Selected)
The ones that earn their mention:
- AI Character Import validation failure — LLMs don't reliably respect character limits, so physical description prompts are now truncated to schema maximums (350/500/750/1000 chars); missing
createdAt/updatedAttimestamps on system prompts and physical descriptions added to the assembled export - Memory cleanup POST returned 400 — the housekeeping dialog was not including
characterIdin the POST body despite the API schema requiring it - Context summary stale for 100 messages — now regenerates at the same checkpoint schedule as titles
- Moderation provider reporting irrelevant categories — 1% relevance floor filters noise
- Job queue UI misleading labels — "Job Payload (sent to LLM)" corrected to "Job Parameters"; human-readable type names for embedding, story background, and danger classification jobs; character names resolved for embedding jobs
- React setState-during-render warning —
handleChangeandonChangecalls moved out ofsetIncludedOptionalsstate updater inJsonSchemaForm - Broken
requirein AI import service — replaced with static import to eliminate Turbopack build warning manage_filestool ID mismatch — renamed tofile_managementto match the actual tool name used by the LLM
Subsystem Table
For those keeping score:
| Name | Function |
|---|---|
| The Foundry | Architecture, plugins, packages, LLMs, API keys |
| Prospero | Projects, agents, tools, files, and the new separate LLM logs database |
| Aurora | Character creation — now with the AI Import Wizard |
| The Commonplace Book | Memory and retrieval — embedding fixes and faster cleanup |
| The Salon | Chat interface — now with Run Tool and message search |
| Calliope | Interface, themes, and the settings page's new visual treatment |
| The Concierge | Content routing, moderation API, and the quiet art of opening the right door |
| The Lantern | Image generation and atmospheric story backgrounds |
| Pascal | Games, randomness, and the quiet mathematics of chance |
| Saquel Ytzama | Encryption, key management, and the Pepper Vault |
Upgrading from 3.0
The database migrations handle themselves, as they always do. The Concierge replaces Dangermouse automatically — your content safety settings are preserved, but the system that enforces them is now competent. Files will be migrated to the new filesystem-backed layout on first startup. LLM logs will be copied to their own database.
If you were running Node.js via npx quilttap, native module resolution is now more robust. If you were running Docker or a VM, nothing changes except that everything behind the walls has been repaired, reinforced, and — in the case of one particular staff member — shown the door.
The house looks the same from the outside. The rooms are arranged as you left them. But the doorman is better dressed, the filing cabinet knows where things are, and the sculptor in the Dressing Room has acquired tools that would make Pygmalion weep. Come in. The Concierge is expecting you.
Installation
macOS
- Download the
.dmgfile and open it - Drag Quilltap to your Applications folder
- Launch Quilltap from Applications
Windows
- Download and run the
.exeinstaller - Follow the installation prompts
- Launch Quilltap from the Start Menu or desktop shortcut
Linux
- Download the
.AppImagefile, make it executable (chmod +x), and run it - Or install the
.debpackage:sudo dpkg -i quilltap_*.deb - Requires Docker Engine — install from https://docs.docker.com/engine/install/
Node.js (any platform)
npm install -g quilltap
quilltapOn first run, the CLI downloads the application files (~150-250 MB)
and caches them locally. Subsequent launches start instantly.
Docker
docker pull csebold/quilltap:${TAG}Or download the quilltap-linux-<arch>.tar.gz rootfs tarball for use with Lima. See the README for setup instructions.