Quilltap 4.7.0
View on GitHub →The house gains a society. Characters write letters to one another, summon one another from lore, and the keeper gets a console that can read the whole building. The Commonplace Book finally recalls the right thing.
4.6 made the house inhabitable — characters became their vaults, the private rooms ran themselves when no one was watching. What 4.6 did not give them was a way to reach one another across the rooms, or a way for the keeper to interrogate the whole building at once, or a Commonplace Book that fetched the memory you actually meant.
4.7 is the release in which the house becomes a society — and the keeper gets a master key.
Where 4.6 furnished the rooms, 4.7 wires the corridors between them and hands the keeper a panel that sees into every one. Three large bodies of work define the cycle: a postal service and a set of social affordances that let characters address each other directly; the Brahma Console, an operator-grade chat with read-only SQL into all three databases; and a Commonplace Book overhaul that mirrors every conversation into each character’s vault and reorders recall around relevance, not just importance. Around those, the Scriptorium learned discretion — per-document flags that decide what a character may read, write, or have embedded — and a long, honest sweep of plumbing closed the gap between what the model can do and what it can see itself doing.
The thesis is simple: 4.6 made it worth living in. 4.7 made the residents able to find, address, and remember one another — and gave the person holding the keys a way to ask the house anything.
The House Learns to Correspond
The Post Office
The largest new social feature in 4.7 is the Post Office: characters can now send and receive Markdown letters, delivered by Suparṇā — a new personified Staff member — and stored in each character’s Mail/ vault folder.
Two new always-available character tools carry the mail. send_mail writes a letter to another character (anyone may write to anyone), and list_email lists a character’s own mailbox with the exact calls to read, answer, or discard each letter. Reading and deleting reuse the existing doc_* tools; replying threads through send_mail’s in_reply_to. A delivered letter lands as Mail/<epochMillis>-from-<sender-slug>.md in the recipient’s vault, with the delivery system owning the frontmatter (from, fromCharacterId, sentAt, alerted, inReplyTo) and the sender writing only the body. After each turn’s Commonplace Book whisper, the Post Office checks the responding character’s mailbox; any unannounced letters trigger a Suparṇā whisper that reads each one aloud, names the sender and date, and flips the letter’s alerted flag.
Mail addressed to a character you play now surfaces too. A letter delivered to a controlledBy: 'user' participant used to sit in its vault unannounced forever, because the delivery whisper only fired during an LLM character’s turn. It now appears the moment you open the chat and within a turn if it arrives mid-session — rendered expanded by default rather than collapsed into a chip, because a letter to your own character is significant.
And you can send mail by hand. A Compose Mail button in the Salon composer gutter (an envelope, “Post a letter”) opens a dialog where the operator posts a letter as one of their player-characters to any character in the workspace — even one not in the scene — optionally quoting a letter from the sender’s mailbox. The UI delivers through the same Post Office service the send_mail tool uses, so the two stay in lockstep, and Suparṇā joins the Insert Announcement “Staff” list so you can post in her voice.
Summon from Lore, and Cleaner Walk-Ons
The Salon’s Add Character dialog gained a Summon from Lore button that opens the Aurora AI-import wizard inside the Salon; on success the summoned character is preselected so you finish the add through the normal controls. In the same pass, the “Create Ad-hoc NPC” dialog stopped silently discarding two fields — every NPC had been losing its Scenario and Physical Description because the dialog sent payload keys the server schema rejected. Both now persist.
The “Play As” controls were unified, too. The Play As (Optional) dropdown on New Chat and “Continue Elsewhere” now offers every character already in the chat, not only those whose default is user-controlled; choosing one switches that participant to controlledBy: 'user' in place. When any user-controlled participant is present, the “Make this an autonomous room” toggle is disabled with an explanation.
The Keeper Gets a Console
The Brahma Console
The Brahma Console is a second floating chat surface, sibling to the Help Chat, reached from a new tetra-radial console icon in the sidebar footer. Unlike the Help Chat — a character answering with help-doc context — the Brahma Console is a plain LLM: pick a connection profile and talk to the model directly. It persists, lists past conversations, and lets you switch the model at any time while continuing the same chat. It forms no persistent memories and carries no page context.
The Console’s defining capability is read-only SQL. Its run_sql tool can query any of the three Quilltap databases — main, llm-logs, and mount-index — and read rows back as JSON, so it can answer questions about characters, memories, documents, conversations, model usage, and costs by translating them into queries. Read-only is enforced at the tool layer by three independent guards: a single-statement write-keyword pre-scan, the authoritative better-sqlite3 stmt.readonly check (fail closed), and a max_rows cap (default 200, hard cap 1000). Writes and schema changes are rejected before they run; BLOB columns come back as a <blob: N bytes> placeholder. The tool is offered only when the Brahma builder’s sqlAccess flag is true and executed only on the operator surface — two independent gates, so no character surface can reach it.
The Console also became reachable from inside a Salon. Through @Brahma: / @Brahma? markup and the ask_carina tool, a console or SQL answer can be dropped straight into a scene, public or whispered. Brahma is a pseudocharacter, not a character — no characters row, no participant, forms no memories — reachable only by the operator, a user-controlled persona, or a character with systemTransparency. A real character named “Brahma” always wins the name.
Throughout the cycle the Console grew the affordances of a real tool: it shows the model’s chain-of-thought in a collapsible “Thinking” panel (the same one the Salon uses), renders run_sql queries and results inline as tool cards with collapsible Query and Result panels, surfaces real database errors instead of a generic failure, copies any message as Markdown, and raised its agentic tool-iteration limit to 25.
The Commonplace Book Recalls Better
Conversation Summaries Become Searchable Per-Character
4.6 confirmed an anomaly in memory recall and deferred the fix; 4.7 carries it out. The work comes in two parts.
Part A mirrors every conversation’s rolling summary into each participant character’s vault, under a Conversation Summaries/ folder. Because vault documents are chunked and embedded, past-conversation summaries become retrievable per-character. Each file carries YAML frontmatter — the conversation UUID, the participating characters, the count of real messages, and the first/last timestamps — and the UUID is the key for replacement, so a regeneration finds and deletes its own prior file even after a rename. Deleting a conversation sweeps its summary out of every participant vault.
Part B makes the recall a character receives before a turn more relevant and more actionable. The recap’s old most-recent-N block becomes two vault-sourced lists — Relevant Past Conversations (semantic search against the current moment) and Recent Conversations (recency-ordered) — each scaling 3→10 entries over a 4K→32K context window, each printing the conversation UUID so the model can pull the full transcript with read_conversation. Inter-character memories are now half importance and half relevance rather than importance-only. The relevant-conversations list refreshes on every summary fold, since relevance drifts as a conversation advances. The Current State block’s clothing summary became a single salience-based sentence instead of a comma-joined rattle of every equipped item. And a new Regenerate conversation summaries button under Settings → Memory backfills the vault files the recall depends on.
This sits atop a two-phase recall-relevance effort that reads the extractor’s targeting tags back at recall time: scope and project gating, temporal down-weighting, context-axis steering, a participant-aware boost for memories about characters in the room, and opt-in one-hop expansion to related memories — all bounded, clamped multipliers on the final blended score, with absent context producing byte-identical historical behavior.
The Scriptorium Learns Discretion
Per-Document Policy Flags
A mounted Markdown document may now carry three frontmatter flags that govern how Quilltap treats it. Each defaults to true and only bites when set false.
embed: falsekeeps the document out of the embedding pipeline and erases any embedding it already has (the chunk text stays; only the vectors are cleared).character_read: falsehides the document from every LLM character — thedoc_read_*tools report it as not-found, listing and grep omit it, and it never surfaces in RAG retrieval. The “not found” message is identical to a genuinely missing file, so a character can’t probe for protected filenames.character_write: falseblocks every character-initiated mutation: write, edit, move, rename, delete, copy-as-source, and any folder operation that would touch the protected file.
character_read is the master gate: when it’s false the other two are forced false as well. The human operator is never restricted by these flags — they govern characters only, and the Librarian stays silent about changes to a character_read:false document so its existence stays out of the characters’ view. The flags are stored on the document’s link row, backfilled from existing frontmatter on upgrade, and re-derived on every reindex, so editing the frontmatter is the control surface.
The Librarian Announces Every Change
Previously, when a character used a doc_* tool to write, edit, move, rename, copy, or create a file, the change happened silently — only deletes, folder operations, and opens posted an announcement. Now every change-effecting doc_* tool posts one, matching the Document-Mode experience you get when you edit a document yourself. Creating a file reports its full contents; editing reports a unified diff (a no-op edit posts nothing); moving, copying, and filing or deleting binary assets each post a note naming the change. Large contents and diffs are capped in the announcement with a link to the full document, so a big change can’t blow the model’s context budget — the document itself is never truncated.
qtap:// — One Address for Any File
Quilltap gained a single, first-class way to address any document the Scriptorium can reach: a qtap:// URI, the existing { scope, mount_point, path } triple folded into one string. Three reserved authorities name the non-store scopes — qtap://self/… (the acting character’s own vault), qtap://project/…, and qtap://general/…. Every doc_* tool now accepts an optional uri parameter that supersedes the legacy locator fields; every tool result, search hit, and self-inventory row carries a uri; and the personified Staff quote documents by URI. The Salon linkifies a qtap:// URI that points to a confirmed, accessible document into a clickable link that opens Document Mode. The CLI accepts a URI wherever it took a mount-and-path. No storage, export, or migration change — the URI is just a serialization of the triple. Relatedly, mount_point: "self" (own-vault access) was taught to and accepted across the entire doc_* toolset, giving every character one stable, rename-proof handle for its own vault.
Groups, Projects, and the Tiered Pool
Groups
A Group is a cross-section of characters, parallel to how a Project is a cross-section of files and chats. Each group owns an official document store — holding a description.md, a Scenarios/ folder, and a Knowledge/ folder — plus zero or more additional linked stores, and surfaces Description, Scenarios, and Knowledge into chats, the Commonplace Book, and the search tool. The scope for a turn is the union of the stores of every group the responding character belongs to, never the chat’s participant set — a character never gains a co-participant’s group stores. Groups participate in .qtap export and backup, and live as a slim identity row in the main DB with membership and store links in the mount-index DB. A Groups section sits above the character grid on the Aurora page, with an editor for name, description, color, icon, members, and linked stores.
Projects Collapsed Into Their Store
Mirroring the 4.6 character-vault cutover, the projects table is now a slim identity row — id, name, officialMountPointId, and timestamps. Everything else moves into the project’s official store as top-level files: description.md, instructions.md, state.json, and one flat properties.json for the fourteen settings fields. userId is dropped entirely; projects are now global to the instance. An overlay re-assembles the hydrated Project on every read and routes store-resident fields back to files on write, so the shape callers see is unchanged. A backup-first, count-guarded migration performs the cutover.
A Single Tiered Mount Pool, and Tri-Tier Wardrobe
The {character, participant, group, project, global} mount-tier resolution that had been re-derived — with subtly divergent dedup rules — in the knowledge injector, the search tool, and the doc-edit path resolver is now single-sourced in lib/mount-index/tiered-mount-pool.ts, and every consumer delegates to it. Wardrobe rode that consolidation to become tri-tier, matching knowledge and scenarios: a character’s wearable garments now draw from the character’s own vault, the active chat’s project stores, and Quilltap General, in that precedence. Project wardrobe gets its own card on the Prospero page and its own CRUD routes, and the wardrobe editor’s new “Add to” selector chooses where a new item is written — this character, shared everywhere, or shared to this project.
Carina Comes of Age
Carina — the inline @Name: / @Name? query system that lets users and characters ask quick questions of a designated answerer without derailing the conversation — landed early in the cycle and was steadily completed. A line now opens when either side qualifies: an answerer can be reached by anyone, a Carina-enabled asker can reach any character, and the operator can always reach anyone. Answers surface in the Salon the instant they return rather than waiting for the post-turn refresh, carried by a new carinaAnswer SSE event. Carina answerers now both form memories from their consultations and draw on what they remember, via a dedicated CARINA_MEMORY_EXTRACTION job and recall injected into the isolated call. And self_inventory gained a carina section so a character can introspect who it can reach.
Wardrobe, Images, and Roleplay Formatting
The wardrobe tools were renamed and consolidated into a consistent wardrobe_-prefixed CRUD-plus-wear set of seven, adding the ability to read item detail, edit stored items, and set the Portrait Cue — a new optional image-generation phrase fed to the avatar and Lantern pipelines in place of the item’s bare title. The redundant wardrobe_items DB mirror table was removed; wardrobe now lives solely in the vault.
Image generation gained a single provider-agnostic way to ask for a shape: a semantic orientation (portrait / landscape / square) that a host resolver maps onto each provider’s real mechanism — a concrete size, an aspect ratio, or prompt wording. Avatars asked for sizes that silently degraded to square on half the providers; now they default to portrait and store the dimensions actually measured from the returned image. Avatars also picked up a dedicated Head & Shoulders physical-description prompt, so avatar generation no longer feeds below-the-crop anatomy that provider moderation rejects.
Roleplay-template formatting, built from three drifted layers, was reworked so a delimiter is defined once and all behaviors derive from it: delimiters became a discriminated union (wrap, linePrefix, tagPrefix), the client and server renderers were unified onto a shared core, and the toolbar’s two insertion paths route through shared pure transforms. Each delimiter then gained a hide toggle, a palette of theme-aware style classes, and layered add-ons (bold, italic, reverse, underline, border, font).
Themes Can Now Redraw Every Icon
A Themeable Icon System
This cycle a theme stopped being merely a palette and a set of fonts: it can now redraw the application’s entire iconography. Quilltap grew a centralized, theme-ready icon system — every glyph in the app routes through a single <Icon> component backed by a registry of 82 canonical icons (the app-wide migration off inline SVGs reached the last app/* page files this cycle) — and the theme manifest gained an icons field. A theme supplies an .svg (masked and tinted by the UI’s currentColor, like every other icon, including the brand mark) or a full-color .webp, and the live theme switch picks it up. Icon-override URLs are version-stamped so re-releasing a theme with a changed glyph can’t leave a browser stuck on the stale one.
Madman’s Box Shows What That Means
The bundled Madman’s Box theme is the proof of the whole idea. It now overrides all 82 canonical icons with original SVGs in its own Art Deco design language — circular glyphs evoking the script of some ancient time-traveling, spacefaring species — sharp butt caps and miter joins, disciplined stroke weights, geometry-only currentColor masks — so the entire interface, not just its colors, is drawn in the theme’s hand. (The new mail and tetra-radial Brahma Console icons shipped this cycle each arrived with a matching Madman’s Box override.) A mechanical lint enforces the SVG contract and reports 82/82 coverage, contract clean. Madman’s Box is a warm walnut-and-brass, dark-only theme with amber tube-glow, phosphor-cyan links, and banker’s-lamp-green success; it also wires all eight subsystem background textures and self-hosts its own font stack, and picked up refreshed Settings backgrounds late in the cycle.
This is the headline reason to open the new theme preview.
The Theme Preview Is Now a Full-Page Contact Sheet
The inline “expanded card” preview in Settings → Appearance became a full-page modal with a Light/Dark toggle that drives both the banner and a live element preview. Crucially for a theme like Madman’s Box, the modal now shows two contact sheets: a gallery of the theme’s bundled images, and an icon sheet rendering each overridden icon in four states — default, muted, hover, and on-primary — inside a theme-scoped container so the bundled override CSS and color tokens are actually in effect. You can see the whole redrawn iconography and image set at a glance before you switch. Banner-header contrast on vivid themes was fixed in the same pass, painting the banner in the theme’s own background color with an accessible computed foreground.
Themes also gained control of per-page backgrounds: the content pages (Aurora, Prospero, Salon, Files, Scriptorium, Photos) now resolve their subsystem background image through the manifest, the same mechanism Settings already used, and a theme can suppress a page’s background entirely by setting it to "none".
The Salon and Quality of Life
In the Salon, the composer font now matches your sent messages, the New Chat dialog lets you layer free-text notes onto a chosen scenario instead of choosing one or the other, projects can set their own default roleplay template, the left-sidebar icons grew a size, and the Aurora header’s Carina and User-controlled toggles now light up when active. Background-job concurrency became adjustable from 1 to 32 via a “Simultaneous Labours” slider. A startup self-heal re-renders and re-embeds conversations the pipeline left half-finished, so the chat list stops accumulating unsearchable chats. And multi-character turns were hardened against hijacking — a structural backstop truncates a finalized response at the first line that opens with another participant’s speaker tag.
Under the Floorboards
The whole client server-state layer was migrated off SWR onto TanStack Query v5 across seven phases, ending with SWR’s complete removal. The CLI reference was extracted out of CLAUDE.md into its own docs/developer/CLI.md. Several god-components were split into focused modules with no behavior change, and a dead-code-and-conformance pass removed 91 happy-path debug log calls and assorted dead locals. The SVAR file manager was integrated end to end behind an opt-in beta toggle, with a qt-* theme bridge so it restyles per theme.
Selected Fixes
- Database-backed document writes now chunk immediately. A text document written into a database-backed store recorded its content but never chunked it, so vault writes — character fields, project description/instructions/state, and the conversation summaries recall depends on — stayed out of semantic search until a manual rescan. Writes now re-chunk inline before emitting the write event. No more manual rescan.
- CLI and server self-heal a stale native-module ABI. After a Node upgrade, the cached SQLCipher binding throws
NODE_MODULE_VERSION; the DB-touching CLI subcommands never reached the launcher’s rebuild and failed with the raw error. A lightweight heal now runs at subcommand dispatch, reading the compiled-for ABI straight from the.nodebinary. The Jest suite self-heals the same way before running. - Brahma Console no longer repeats a query and burns its turn cap. The agent loop rebuilt each turn without the model’s tool-call context, so a reasoning model re-derived the opening step every turn until it exhausted the cap. Tool turns are now threaded the way the Salon does it, via a shared helper.
- Help Chats and the Brahma Console are never moderated. The Concierge now leaves utility chats alone entirely — no classification, flagging, rerouting, or announcements — gated by a single exemption predicate at every entry point.
- SillyTavern export mislabeled every speaker in multi-character chats. The export stamped the first character’s name on every assistant line; it now resolves the speaker per message from a participant-id → name map.
- Opening Document or Terminal Mode erased unsent composer text.
SplitLayoutremounted the Lexical editor on every mode change; it now keeps the chat pane mounted in one stable structure and varies only width and visibility. - Character Rename/Replace failed with “Unknown action: rename.” The action was lost in the v1 API cleanup; it was restored against the post-4.6 data model and now sweeps the character’s fields, memories, and the titles and bodies of every chat the character appears in.
- Character avatars follow the character’s pronouns. The avatar prompt now anchors apparent sex from the character’s standard pronouns, so a gender-neutral description plus an outfit cue no longer renders the wrong sex.
- Scheduled autonomous rooms could wedge “running” with no turns. The scheduled run-start ran in the forked child where the
currentRunIdwrite was buffered; it now funnels through the same parent-ordered start as the manual path, plus a defense-in-depth self-heal sweep. - Autonomous-room renames fired nearly every turn. The interchange counter was counting staff whispers; it now skips any message with
systemSenderset, restoring the roughly-every-10 cadence. - Embedding failures no longer pile up as DEAD rows. Deterministic failures (empty input, non-finite vectors, over-context) are dropped instead of retried three times each; the Ollama plugin validates input and output vectors.
- Several illegibility fixes on bold-accent themes. Keyword pills, badges carrying
qt-text-xs, and user messages in the Help Chat and Brahma Console all went low-contrast on themes like Madman’s Box; each was swept and fixed app-side, preserving the loud-accent identity. - Carina answers no longer fail with “empty response” when the answerer thrashes tools. A tool-eager model that exhausts its iteration budget without composing prose now gets one final forced-text turn with no tools offered.
- Project default image profile and roleplay template now survive import/restore. Both were dangling after an import into a different instance; both are now remapped.
- New characters keep the identity typed into the create form. The create schema never declared
identity, so Zod stripped it and the vault’sidentity.mdwas written empty; added to both the create schema and the wizard path.
Upgrading from 4.6
The database migrations handle themselves on first startup, but 4.7 contains a consequential project-store cutover and several store-backfill passes. As with the 4.6 character-vault cutover, treat this as a data-location migration, not merely a schema migration.
Before upgrading, make a current backup:
npx quilltap db backup
The project cutover migration is backup-first and count-guarded, and the wardrobe-table drop runs only after both one-time wardrobe-population tasks have completed. Several other migrations run automatically: the Groups tables, the Carina flags and message metadata, the Brahma Console connection-profile field, the per-document Scriptorium policy flags (which de-embed embed:false documents on upgrade), connection-profile unique-name de-duplication, and the roleplay-delimiter-kinds tagging.
If you operate external backups, verify that the project and group document-store data is included alongside the database — after this release, a database backup without the corresponding store data is not a complete backup. The conversation-summary vault mirroring backfills lazily; you can also force it from the new Regenerate conversation summaries button under Settings → Memory.
Node.js 24+ is still required, unchanged from 4.6.
Installation
Electron Desktop App
Download the latest .dmg (macOS), .exe (Windows), or .AppImage (Linux) from the quilltap-shell releases page.
npm (Node 24 required)
npm install -g quilltap
quilltap
Open http://localhost:3000 in your browser. Requires Node.js 24+. First run downloads ~150–250 MB and caches locally.
Docker
docker pull foundry9/quilltap:4.7.0
Or use the startup scripts:
# Linux / macOS
curl -fsSL https://raw.githubusercontent.com/foundry-9/quilltap-server/refs/heads/main/scripts/start-quilltap.sh | bash
# Windows (PowerShell)
irm https://raw.githubusercontent.com/foundry-9/quilltap-server/refs/heads/main/scripts/start-quilltap.ps1 | iex
Standalone Tarball
Available for environments where npm global installs and Docker are both impractical. See the GitHub releases page for download links.
The house is the same house, but the residents can find one another now. A character can post a letter and trust it will be read aloud; can be summoned out of lore into a scene already in progress; can ask a colleague a question without stopping the conversation, and be remembered for having asked. The Commonplace Book no longer hands a character the loudest memory when it meant the most apt one. The Scriptorium can keep a document private, or read-only, or unembedded, by a single line of frontmatter. And the person holding the keys can sit at the Brahma Console and ask the whole building a question in SQL, safely, read-only, with the model’s reasoning laid out beside the rows.
4.6 answered the inhabiting fears. 4.7 answers the social ones: can the residents reach each other, can they remember the right things about each other, and can the keeper see all the way down without putting a single byte at risk.
— Ariadne, for the Bureau, June 19, 2026