Quilltap 3.2.0
Quilltap v3.2.0 Release Notes
The doors are locked, the characters have arrived with their portraits, and someone has given the machines a language they can speak.
There are two kinds of security. The first is the kind you perform — the deadbolt turned with a satisfying click, the chain drawn across, the nightly ritual of checking windows. The second is the kind that was always there but you only notice when someone tells you the walls have been replaced while you were sleeping.
Quilltap 3.2 is the second kind.
Your databases are now encrypted. They have been, in the strict sense, ever since you upgraded and the converter ran silently during startup — rewriting every table in cipher, storing the key, sweeping away the plaintext like a valet removing evidence of yesterday's outfit. If you did not notice, that was the intention. If you did notice, you are either unusually attentive or you set a passphrase, in which case the Estate asked you for it before letting you through the front door, and you know exactly what happened.
But encryption alone does not fill a release. Behind the sealed doors, the staff have been productive. The LLMs can now run shell commands inside the sandbox — a development that Prospero regards with the measured enthusiasm of someone who has been asked to supervise a demolition crew and has decided the best approach is a very good clipboard. Characters now arrive with portraits. The Commonplace Book has learned to weigh her memories by age. The LLM Inspector exists — a slide-over panel that shows you everything the Estate says to the providers and everything they say back, which is either deeply reassuring or mildly alarming depending on your temperament.
If 3.0 was the move and 3.1 was the furniture, 3.2 is the season where the locks went on, the portraits went up, and the staff acquired skills that their previous job descriptions had not anticipated.
What Changed (The Executive Summary)
Every database file is now encrypted at rest with SQLCipher, with optional passphrase-locked mode for those who want a gate as well as a wall. Shell interactivity gives LLMs the ability to execute commands inside the VM or Docker sandbox — six tools, a workspace watcher, and a sudo approval modal. The LLM Inspector Panel provides a chronological record of every API call in a chat session, accessible from the toolbar or a keyboard shortcut. Provider icons and per-message model badges now show you which provider generated each response. Connection profiles gained drag-and-drop custom sort order. Memory weighting applies time-decay so old memories fade gracefully rather than persisting at full volume forever. Seed characters ship with avatars. Multi-character identity anchoring prevents weaker models from responding as the wrong person. Pronoun injection ensures the memory system gets names and pronouns right. The README was updated, the setup wizard was fixed, and a gentleman named Ben was asked to leave.
Saquel Ytzama's Locks
Database Encryption at Rest
The Vault of Whispers has been waiting for this since the Estate was built.
Every Quilltap database — your chats, memories, characters, API keys, LLM logs — is now encrypted on disk using SQLCipher (AES-256-CBC). The encryption key is automatically generated on first installation and stored in a .dbkey file in your data directory. No configuration required. No environment variables to set. The converter runs at startup for existing installations: your plaintext database is rewritten in cipher and the old version removed.
The standard sqlite3 command-line tool can no longer read these files. This is the point.
Locked Mode
For those who share a machine or simply prefer that the Estate not open without being spoken to: a passphrase, processed through six hundred thousand iterations of PBKDF2 before it touches the key. Enable it in Settings → Data & System. When locked mode is active, Quilltap presents an unlock screen on launch. The database does not yield until the correct word is given.
The passphrase can be changed, added, or removed from the same settings card. Changing it re-wraps the key without re-encrypting the database — the operation is atomic across both the main and LLM logs .dbkey files.
Security Refinements
The .dbkey file no longer includes a hasPassphrase flag, which had the unfortunate property of telling anyone who found the file whether a passphrase existed — the informational equivalent of labeling your safe "CONTAINS VALUABLES." The startup sequence now tries the internal passphrase first and falls back to prompting the user, making the flag unnecessary.
The encryption converter was also hardened against macOS file coordination locks. iCloud sync, Spotlight indexing, and other enthusiastic system services could hold locks that prevented in-place conversion. The converter now works on a temporary copy and swaps the result, which is the kind of solution that seems obvious in retrospect and was not obvious at all at 2 AM when the bug report arrived.
A new quilltap db CLI subcommand allows querying encrypted databases from the terminal, for those who need to inspect their data outside the application.
Legacy Migration
Users upgrading from installations with the old pepper_vault passphrase system are handled transparently. The unlock endpoint detects the legacy scenario, routes through the existing pepper, and automatically migrates to a .dbkey file. API keys that were left as encrypted ciphertext after a previous migration are detected and decrypted on startup; keys that cannot be recovered trigger a toast notification advising re-entry in Settings.
Prospero Opens the Terminal
Shell Interactivity
The LLMs have been given hands.
Six tools — chdir, exec_sync, exec_async, async_result, sudo_sync, and cp_host — allow characters to execute shell commands inside Lima VM and Docker sandbox environments. The workspace is acknowledged via a modal before first use. Sudo commands require explicit approval. The system includes a command warning mechanism for suspicious commands, because giving an LLM root access without guardrails would be the kind of decision one regrets at leisure.
An Electron workspace file watcher monitors changes with binary detection and OS quarantine markers, so files created or modified inside the sandbox can be safely surfaced to the host. An async process registry manages background commands.
The shell tools documentation notes — sensibly — that packages installed via apk add or apt-get inside Docker containers are ephemeral and lost on restart, and suggests keeping a setup script in the workspace or building a custom image.
The Inspector Arrives
LLM Inspector Panel
There has always been a way to see what Quilltap sends to the LLMs and what they send back. It was a modal, scoped to a single message, opened via a button that required you to know it existed.
The Inspector Panel replaces this with a proper instrument. A slide-over panel accessible from the chat toolbar (the terminal icon) or via Cmd+Shift+L / Ctrl+Shift+L, it shows every LLM interaction for the current chat in chronological order: chat messages, tool continuations, memory extraction, title generation, danger classification, context compression, and every other background event that touches a provider.
Each entry is a collapsible card with a type-colored badge, provider and model identification, token counts, and expandable detail views showing the full request and response. Client-side filtering by category lets you see only what you're looking for. Opening the panel from a per-message "View LLM logs" button scrolls directly to the relevant entry.
The old modal is preserved for Settings and Character pages, where per-chat context does not apply.
Expanded Log Coverage
All eighteen cheap LLM task functions — memory extraction, title generation, summarization, compression, image prompt crafting, scene context derivation, and the rest — now thread chatId through to the logging system, so their calls appear in the Inspector. The Concierge's OpenAI moderation API calls are logged as well. LLM log entries for chat messages now populate characterId, making it possible to trace which character each entry belongs to. And logs no longer truncate message content to 500 characters; the full content is stored, with the UI showing an expandable preview.
Aurora's Portraits
Provider Icons and Model Badges
You can now see at a glance who is speaking — not just the character, but the machine behind the character.
Assistant messages record which provider and model generated them, persisted in the database and included in exports. Provider SVG icons from plugins flow through the /api/v1/providers API to the frontend. A new ProviderModelBadge component displays the provider icon and model name beneath chat avatars, in the participant sidebar, on homepage and Aurora character cards (via default connection profile), and in new-chat and add-character connection profile selectors.
Old messages gracefully show no badge. The new ones arrive with their credentials visible, like guests at a party wearing tasteful name tags.
Multi-Character Identity Anchoring
In multi-character chats, weaker LLMs had a tendency to respond as whichever character seemed most interesting at the moment, regardless of whose turn it was. An assistant prefill message — [CharacterName] — now anchors the model's identity before generation begins. The prefix is stripped from the displayed response by the existing stripCharacterNamePrefix() cleanup, so the fix is invisible to the reader and effective for the model.
Pronoun Injection
When a character has pronouns set, they are now appended after the character name in all cheap LLM memory extraction prompts — user memory, character memory, and inter-character memory. This prevents the memory system from generating entries like "He mentioned his fondness for gardening" when the character in question uses she/her. Pronouns appear in the PARTICIPANTS context block, conversation transcript labels, TARGET CHARACTER lines, and inter-character observer and subject headers, via a shared formatNameWithPronouns() utility.
The Commonplace Book Learns to Forget
Memory Weighting with Time Decay
The Librarian has always kept everything. Every memory, once written, persisted at its original importance until housekeeping removed it — which meant a memory from three months ago about the weather carried the same weight as a memory from yesterday about a character's secret. This was, the Librarian insists, a principled position. It was also wrong.
A new calculateEffectiveWeight() function combines base importance with exponential time decay — a 30-day half-life, configurable importance floor — using max(createdAt, lastReinforcedAt) as the reference timestamp. Passive retrieval no longer resets the decay timer, because reading a memory is not the same as the memory mattering again.
The weighting integrates into three systems: semantic search ranking (60% cosine similarity, 40% effective weight), context injection sorting (weight-primary with score tiebreaker), and housekeeping hard-cap enforcement. Memories injected into LLM context now include relative age labels — [yesterday], [3 weeks ago], [2 months ago] — so the model can distinguish recent knowledge from ancient lore.
Connection Profiles in Order
Custom Sort Order
Connection profiles now have a persistent sortIndex field with drag-and-drop reordering in Settings via @dnd-kit. A "Reset Sort Order" button restores the default arrangement: default profile first, cheap last, alphabetical in between. All profile dropdowns and selectors across the application honor the custom sort order, so the provider you use most can always be at the top of every list.
The Welcome Committee
Seed Characters with Avatars
Fresh installations now greet you with Lorian and Riya — two seed characters imported via .qtap bundles on first startup, complete with 42 memories between them and avatar images that are uploaded to file storage and set as their default portraits. The previous seed character, Ben, has been removed — a decision that required fixing an early-return bug in seedInitialData() that prevented .qtap imports from running when no JSON seed characters existed.
Setup Wizard Fixes
The selectable options in the setup wizard — embedding provider, image provider, provider selection, profile archetype — no longer all appear selected simultaneously. The phantom qt-bg-active and qt-bg-hover classes that caused this have been replaced with proper qt-option-selected and qt-option-unselected utility classes that provide clear visual distinction. Six wizard components were updated.
A first-startup race condition where the page rendered without a sidebar and failed to redirect to the setup wizard has been resolved. The session provider now keeps "loading" status on 503 instead of "unauthenticated," and PepperVaultGate retries on failure instead of giving up.
The Plumbing
Contextual Help Routing
All 69 help files now carry YAML frontmatter with a url field mapping each document to its corresponding UI route — /aurora/:id/edit, /settings?tab=providers, * for global topics. The help bundle is at version 3.0.0. This infrastructure enables future contextual help: the right documentation for the page you're actually on.
Workspace Path Semantics
LLM shell tool descriptions now explicitly state that paths are relative to the current workspace directory, that absolute paths with a leading / refer to the VM root filesystem and will be rejected, and that workspace: prefixes in cp_host should use relative paths. The previous descriptions left this to inference, which is never advisable when the audience includes language models.
Streaming Error Recovery
User messages no longer vanish from the chat UI when the LLM provider returns a streaming error. The backend already persisted the user message before streaming began, but the frontend was removing the optimistic message on error. It now re-fetches the chat to sync with the saved server state.
Chat Composer Auto-Focus
The chat composer textarea now auto-focuses when it's the user's turn. The page's inputRef had never been connected to the actual textarea DOM element — ChatComposer created its own internal ref — so all post-generation focus calls were no-ops. A new inputRef prop threads the connection through.
Build and Release
The README was expanded: the plugin types table grew from 5 to 7 entries (adding Image Provider and Embedding Provider), and a engines field now requires Node.js >= 24.0.0. The pre-commit hook was streamlined from 12 steps to 4, with lint, test, tsc, and build responsibilities moved to the /commit command. A Discord commit notification webhook was added to CI. Legacy JSONL file records and their physical storage were removed from the source tree — 14 orphaned records and one image that the migration was faithfully importing into every fresh database.
Approximately 85 development logger.debug calls were removed from 30 files. Seven components had raw Tailwind color classes converted to qt-* semantic theme classes. The folders table was added to backup and restore. Unused dependencies (@aws-sdk/client-s3, svgo) and a stale file (ai-import/index.tsx) were removed. API docs were updated to v3.2. The dead code report was refreshed. The backup help documentation was expanded with a comprehensive list of included and excluded data.
Bug Fixes (Selected)
The ones that earn their mention:
- Passphrase unlock fails during legacy migration — when a user had a passphrase in the old
pepper_vaultbut no.dbkeyfile, the unlock endpoint rejected the attempt because the dbkey module's internal state wasneeds-setupinstead ofneeds-passphrase; now detects the legacy scenario and routes correctly - Database encryption fails on iCloud-synced directories — the converter now works on a temporary copy to avoid file coordination locks from iCloud, Spotlight, and other macOS services
- Encrypted API keys left as ciphertext after migration — keys that survived the
drop-api-key-encryption-columnsmigration as encrypted blobs are now detected and decrypted on startup; unrecoverable keys trigger a user-facing notification - Multi-character identity confusion — weaker LLMs responding as the wrong character now anchored by assistant prefill
- Assistant prefill trailing whitespace — the identity anchor
[CharacterName]had a trailing space that Anthropic's API rejected; removed - Setup wizard options all appear selected — phantom CSS classes replaced with proper selected/unselected states
- First-startup race condition — session provider and PepperVaultGate now handle 503 correctly
- Seed data files not found —
process.cwd()replaces__dirname, which Next.js rewrites to.next/dev/server/ - Legacy JSONL records polluting fresh installs — orphaned file records removed from source tree
hasPassphraseflag in.dbkey— security-sensitive flag removed; startup logic adapted
Subsystem Table
For those keeping score:
| Name | Function |
|---|---|
| The Foundry | Architecture, plugins, packages, LLMs, API keys |
| Prospero | Projects, agents, tools, files — now with shell interactivity |
| Aurora | Character creation, AI Import Wizard, provider badges, identity anchoring |
| The Commonplace Book | Memory and retrieval — now with time-weighted decay and pronoun-aware extraction |
| The Salon | Chat interface — streaming error recovery, composer auto-focus, LLM Inspector Panel |
| Calliope | Interface, themes, and the setup wizard's new visual clarity |
| The Concierge | Content routing, moderation API, logged to the Inspector |
| The Lantern | Image generation and atmospheric story backgrounds |
| Pascal | Games, randomness, and the quiet mathematics of chance |
| Saquel Ytzama | Encryption, key management, SQLCipher, locked mode, and the .dbkey covenant |
Upgrading from 3.1
The database migrations handle themselves. Your plaintext databases will be encrypted on first startup — the converter runs before anything else, and the result is seamless. A .dbkey file will appear in your data directory. Back it up. A database without its key is sealed permanently, and no one — not the Foundryman, not Saquel Ytzama, not you — can open it.
If you had a passphrase in the old pepper vault system, it will be recognized and migrated. If your API keys survived a previous migration as encrypted ciphertext, they will be detected and decrypted. If neither of these situations applies to you, the upgrade will be the quietest event of your week.
Shell interactivity, the LLM Inspector, provider badges, memory weighting, and the rest arrive without ceremony. The seed characters will not appear if you already have characters — they are a first-run courtesy only.
The Estate looks the same from the garden path. The windows glow as they always have. But behind the glass, every room has been sealed by someone who understands what quiet is for, the characters have acquired faces, the machines have been given hands, and the Librarian has finally conceded — privately, and with conditions — that some things are meant to be forgotten. Come in. You'll need your key.
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.
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.