Quilltap 4.10.0
View on GitHub →The house learns stagecraft. You can pass a note to the cast from the wings, give a character instructions for one performance only, set a clock running that she will hear every turn, and have the Host research and draft the set before the curtain goes up. The running order is drawn once per cycle and shown honestly; a paused room stays paused; and every model tried for a reply is listed under the avatar, failures struck through. The Concierge is rebuilt around the one fact he can actually observe — a provider saying no — with three states instead of four, a Settings tab of his own, and a Try Uncensored button for when you disagree with the provider. The shipped prompts stop letting a cast turn into a committee that governs your character, and the memory extractor stops recording silence as consent. The transcript arrives in every open tab whether or not that tab asked for it; every image in a chat gathers into one gallery; avatars are cached per outfit. Below stairs the databases take about a quarter less disk, message search becomes an index probe, `quilltap sync` mirrors a document store to a folder, an unknown API action can no longer delete a project, and a Mac changing its own hostname no longer makes Quilltap shut down its database.
4.9 made the house attentive. Word of a finished errand or a freshly painted backdrop reached every window the moment it happened, the clocks turned over together, and the residents were finally addressed to their faces. What 4.9 could not do was let you direct any of it. You could write a scene and watch it play; you could not lean in from the wings and tell one actor something the others did not know, or set a clock running on her, or have someone build the set before the curtain rose. And when the cast of a long evening began voting on what your own character was allowed to do, there was nothing in the house that told them not to.
4.10 is the release in which the house learns stagecraft.
Where 4.9 made the rooms pay attention, 4.10 hands you the stage manager’s book. Five bodies of work define the cycle. Direction: Inform passes a note from the wings to one actor or all of them; subprompts give a character extra instructions for one chat only; progressions put a clock on a character that she hears every turn; and the Scenario Builder sends the Host off to research and draft the opening scene. The running order: a multi-character room now draws its speaking order once per cycle and keeps it, so the sidebar shows the real sequence, Skip passes the right turn, and a paused room stays paused. The Concierge, rebuilt from the ground up around refusals rather than classification, with three states instead of four and a Settings tab of his own. Whose story it is: every shipped prompt, every character generator, and the memory extractor now hold the line against “the committee” — a cast that drifts into governing your character with sign-offs, conditions, and agreements you never made. And a lighter house: about a quarter off the database on disk, message search turned into an index, and conversation text compressed.
Five more earned their places. Every assistant message now keeps its route trail, so a reply that took three providers to land no longer looks like one that went through on the first ask. The Salon transcript became a subscribed read, which means a reply finished while you were in another tab, or while the laptop slept, now actually appears. The chat gallery gathers every image a conversation ever produced, from nine different sources, into one grid. Avatars are cached per outfit, so putting the coat back on brings back the same portrait rather than paying for a new one. And quilltap sync keeps a document store and a folder on disk in step, in both directions, so you can edit a vault in your own editor.
Two things change in ways worth reading before you upgrade, and both are covered under Upgrading: the Concierge’s four per-chat states become three, and the global Concierge mode is retired; and a project’s character roster, which had been stored and displayed for some time without ever being enforced, now governs which characters may reach that project’s files and wardrobe.
The thesis is simple: 4.9 made the rooms notice things. 4.10 lets you direct what happens in them.
Notes From the Wings
Inform
The Salon composer has a new Inform button — the i in the left gutter, beside Pascal. It opens a dialog where you choose one, several, or every LLM-controlled character in the chat and write a short second-person passage: You notice the clock on the mantel has stopped. Each character you target receives exactly that passage on their next generation, and then it is spent.
Nothing is added to what you write — no Host voice, no preamble, no instruction to keep it quiet. The passage is delivered at the very end of the character’s context, after recall, mail, and progressions, under a single fixed header stating that what follows is true, is already known to the character, and overrides any older memory or record that contradicts it. That header earned its place the hard way: an early build delivered informs higher up the prompt, where a long history buried them, and a character whose own recall disagreed with the note simply disbelieved it. The passage itself is still verbatim, and because it rides in the uncached tail, the system prefix is byte-identical with or without one — prompt caching is unaffected.
The transcript keeps a record — a Host message carrying what you typed, public when you targeted everyone and whispered to the targets otherwise, with a chip reading out of character — but the record never reaches a model. It is stripped from every character’s history, from the summarizer and the other cheap tasks, from the Courier, and from memory extraction. A chip above the composer names who is still owed an inform, with the first line on hover and a × to withdraw it.
Delivery is tied to a saved reply. A provider failure that saves nothing, or a turn the character passes on, leaves the passage waiting for the next attempt. Regenerating or swiping re-applies whatever that generation originally saw and consumes nothing new, so a fresh inform is never spent on a re-roll. Several pending passages for one character arrive together, in order. Autonomous rooms deliver on their next chained turn.
Standing informs. A “Keep it standing in this chat” checkbox, off by default, makes a passage persist: it is delivered on every generation its targets make in that chat until you withdraw it. It is scoped to the chat — it does not change the character, and it does not follow her into other chats, merges, or continuations. Standing passages are ordered ahead of one-shot ones so the block’s prefix stays stable.
Informs survive .qtap export and import and backup and restore, consumed ones included, so swipes stay honest after a round trip. One known limit: pending informs do not travel through a chat merge or Continue Elsewhere.
Subprompts
A character can now carry subprompts: short optional instructions, kept as Markdown files in a Subprompts/ folder at the root of her vault, written in the second person like a system prompt. Which ones are in play is chosen per seat, per chat — from a Subprompts dropdown under each character’s system-prompt selector in the New Chat dialog, or on the participant card in the Salon’s Participants drawer. Every dropdown offers New subprompt…, which opens the editor in place and ticks the new one on.
Selected subprompts render immediately after the system prompt as an ## Additional Instructions block, and in the greeting, and a character dressing herself in the green room sees them alongside her dressing instructions. Changing the selection recompiles that seat’s cached identity stack; editing a subprompt recompiles every chat carrying it; deleting one strips it from those seats first. A seat with none selected produces a byte-identical prompt. They are managed on the Aurora System Prompts tab, beneath the primary prompts, and the AI Wizard and the Optimizer leave them alone.
The use is the obvious one: the instructions that belong to this story rather than to the character — she is travelling incognito, he has lost his voice, play this one as a farce.
Progressions
A character can now carry progressions: named spans of time with a start, an end, and rules for how often to report on them. A pregnancy that began on the first of August and is due on the first of May. A cannon that takes ten minutes to recharge. A cask that will be ready in three weeks. On every prompted turn Quilltap works out elapsed time, remaining time, and percentage from the wall clock and appends a second-person report to the character’s context — so the model never has to do the arithmetic, and never has to remember that time has passed.
Each entry declares the unit it reports in, from seconds to years, and a cadence: every turn, whenever the whole-unit count ticks over, or at most once per wall-clock period. An entry that has just started, just finished, or just been changed reports on the next turn regardless. Spans read as whole units plus a remainder in the next finer one — 20 weeks, 3 days — and months and years are fixed-length, so the sums are deterministic and calendar-free. An entry may carry a description, a quantity (0.3/1.0 MJ), and its own report template.
Progressions live under one reserved key in the vault’s metadata.json, with a published JSON schema; a malformed entry is dropped alone with a warning and the rest survive. They are edited from a new Progressions card on the System Prompts tab, with a live preview of the exact line the character will read. The model cannot set its own: there is no tool for it. Only you, and Pascal’s server-side effects, write them.
That last part is where it becomes more than a reminder. Pascal custom tools gain a progress subject for conditions and availability gates, {{progress.<id>.<field>}} and {{now}} placeholders, and progress.* effect targets — so a tool can be withheld until a recharge completes, and a successful roll can re-arm it with {{now}} and {{now}} + 600000. Pascal’s Workbench knows all of it.
The report rides in the uncached tail of the prompt and is never stored as a message, so caching is untouched and the prompt path performs no writes.
Speaking for Someone Else
Taking up another character’s pen with Impersonate used to post your line exactly as typed, in your voice rather than hers. A new setting under Settings → Chat → Composer offers three modes: Never, Ask each time, and Always restate. With Ask, an impersonated line opens a review dialog on your draft and calls no model at all until you press Restate in their voice; Send as written is the primary button, and Cmd/Ctrl+Enter sends the draft untouched. With Always, the restatement starts the moment the dialog opens.
The restatement is done by the seat’s own connection profile and system prompt, with the chat’s roleplay template, Taboo phrases, standing instructions, the last dozen played messages shaped as that seat would see them, and a Commonplace Book recall against the draft. A chat on the uncensored route keeps to it; a refusal surfaces as an ordinary preview failure and never escalates by itself. The gate never fires for a seat you already control, for an attachment-only send, or for a Carina @Name: address, which is machinery and has to survive verbatim. Its log rows are filed under a new VOICE_REWRITE type — as is the Insert Announcement rehearsal, which since 4.4 had been quietly filing itself as a chat summary.
Naming Names
Typing @ at the start of a word in the composer opens a menu of characters — the chat’s cast first, then everyone else not archived — filtered as you type. Enter, Tab or a click completes the name. In the middle of a sentence the @ is dropped and you are left with the plain name; at the start of a line it is kept only if the name is followed by : or ?, which is a Carina or Brahma query, and the menu offers Brahma there too. Names the Carina parser cannot address lose the @ immediately, rather than leaving you to discover that later.
Setting the Scene
The Scenario Builder
Beside the scenario text on the New Chat form, and in the Salon sidebar’s Scenario control, there is a new button: Ask the Host to set the scene. Give it a mode — a real place or an in-world one — a location, a time, and any details you care about, choose a connection profile, and the Host goes off to research it with the tools you can see him using: your document stores, and for a real place, web search and fetch where the profile allows them. In-world scenes never touch the web.
What comes back is a draft of roughly a thousand tokens or fewer, in the present tense, deliberately written without the cast — no names and no placeholders, so the scene can be reused. You can edit it, ask for a revision, Use this scene, or Save as scenario… to General, a project, a group, or a character’s own shelf. The research is scoped to what the chat itself could see: the cast’s vaults, their groups’ stores, the project, and Quilltap General. Nothing is persisted but the log rows.
The builder also lives on the scenario shelves themselves — the General Scenarios page, a project’s Scenarios card, and a group’s — where it saves rather than uses, and offers every possible home. Groups, which had a scenarios API and no scenarios page, gained a Scenarios card in the process.
A Scenario Is Not a Summary
Starting a new chat could produce a greeting set somewhere other than the scenario you chose — a character welcoming you to a swimming hole when the scene was an office. The scenario in the interface was right; the model was reading a different one. Chat creation had been writing the chosen scenario into two places: where scenarios belong, and into the column that holds a summary of what has been said, a habit left over from before the scenario had a column of its own. Everything reading that second column therefore treated a brand-new chat as already summarized, and the greeting’s “Recent Conversations” section — which inlined it whole — handed the character the full text of some other chat’s scenario immediately before the instruction to greet. On one test instance, 186 of 712 chats were in that state.
Creation no longer writes it, the greeting’s recent-conversation entries are length-capped, and a migration clears the seed wherever it is byte-identical to the chat’s own scenario. Real summaries are untouched. Imports and restores apply the same rule, so an old bundle cannot bring it back.
The Summary Knows Who Is Speaking
The running summary folds batches of turns into a five-section record. The transcript it was handed was labelled USER: and ASSISTANT: while the prompt above it asked for character names, so in a chat where nobody happened to say a character’s name aloud — a two-seat chat between a character and your persona is the usual case — the model was asked to name a speaker it had no name for, and obliged. Every later fold carried the invention forward and then read the real name, when it finally appeared, as an alias. Fold transcripts now carry speaker names resolved from the chat’s seats, including seats that have since left; an unresolvable seat is labelled User or Character, and the prompt says to keep such a label rather than invent one.
Existing summaries are not rewritten, because a wrong name cannot be told apart mechanically from a right one. For a summary that is already wrong, Rebuild Summary… in the sidebar’s Organize drawer discards it and lets the normal fold cadence rebuild it from the first turn.
Leaving People Behind
Continue Elsewhere carried the conversation across but quietly dropped the lines of anyone not seated in the new chat while keeping everyone else’s lines to them — so the new cast read an absent character as present and silent. One autonomous room, continued from a chat its operator had been in, spent sixteen turns addressing him. The Host now names everyone left behind, once, after the replayed messages. A related gap closed with it: in an autonomous room the operator’s own persona was the one absent person the Host could never introduce as absent.
Whose Story It Is
The Committee
A long multi-character roleplay has a particular way of going wrong. Over enough evenings the cast stops behaving like people and starts behaving like a governing body: votes are taken on what your character may do; actions require a sign-off; temporary measures become standing conditions; your own narration of what happened is corrected against somebody’s notes; and a proposal you never answered is remembered, permanently, as something you agreed to. Each step is reasonable in isolation. Together they turn your story into a committee meeting about your story.
4.10 addresses it in four places at once.
The shipped prompts. All twenty-one sample system prompts now carry a short universal block: your narration is what happened, including out-of-character stage directions in whatever marking the chat uses; check the conversation before correcting you; do not invent setting facts to win a point; agreements that bind you exist only in your own plain words; and disagreement never becomes procedure. Each prompt’s list of failure modes gains the committee, and every existing “hold your position” line gains a counterweight. The companion, platonic, and romantic samples add a trust disposition — your judgment is the starting point, and in a crisis they back you first — while the general-purpose sample stays relationship-neutral.
The generators. The AI Wizard and Summon From Lore write the same safeguards into the system prompts they generate, adding the trust disposition only when the source material makes the character your companion, partner, family, or crew. The Character Optimizer treats committee behaviour as drift to correct, may never propose a rule constraining your persona, and may not weaken the safeguards when it refines a prompt. The External Prompt generator uses the shared field semantics for the first time.
The memory extractor. Agreements are now recorded only on the agreeing party’s own words of assent. A proposal is recorded as a proposal, attributed to whoever made it; silence, an apology, or the exchange ending is not assent, and the memory says so — “…; she had not yet responded.” Stated limits are kept, so “until breakfast” does not become “from now on.” Existing memories are left alone; Regenerate memories re-extracts under the new rules.
The scene note. On a chained multi-character turn — no new message from you, but a character has already replied to your last one — the next character is told in one sentence that your latest message is the current state of the scene, and which seat wrote it. It never appears for the first responder or in a one-character chat, and the context is byte-identical where it does not apply.
Listening, and Matching Register
The same twenty-one prompts were also taught to listen. A character now answers what the speaker means: a joke is answered in kind, exaggeration is not taken literally, an offhand remark is not mined for subtext, and when it is unclear whether you are serious, she asks. Replies are sized to what prompted them; paraphrasing your line back to you is out; signature habits and the “not X — Y” construction are rationed; and formal language is kept for moments that want it. Each prompt closes with short example exchanges — a joke caught, a casual line answered briefly, a serious moment — every file with different lines, all marked as showing shape rather than voice.
The generators carry the same direction, and generated example dialogues must cover all three registers. The Optimizer no longer enshrines a habit that appears in most replies as a personality trait.
The prompt seeder used to insert built-in prompts only when they were missing, which meant a revised sample never reached an existing installation. It now refreshes built-in rows when the shipped text changes. Prompts you have imported into a character are copies, and are not touched.
The Running Order
Drawn Once per Cycle
A multi-character room now decides its whole speaking order when a cycle begins — a talkativeness-weighted permutation of the present seats — stores it, and follows it seat by seat until the cycle wraps. The distribution of orders is exactly what it was, drawing the permutation up front and drawing it one place at a time being the same procedure; what changes is that the order is knowable. The sidebar’s Participants list shows the real sequence instead of a talkativeness-sorted guess. Position three now means third.
Cast changes mid-cycle are repaired rather than redrawn: a departed seat is skipped, and a newcomer joins at the back and is dealt in properly next time. The manual queue still jumps the line, and a summoned character is struck from the remaining order so nobody speaks twice. Existing chats carry on without any migration of their turn state.
Getting there flushed out three defects in the old arrangement. The talkativeness of a character you drive had never counted at all — four of the six paths that pick a speaker built their character map from LLM-controlled seats only — and the single shared loader that replaced all six also cut a four-seat room’s selection from about forty-eight queries to twelve. The turn banner named whoever you had last written as rather than whoever held the floor, so Skip recorded a character “declining the floor” who had just spoken and left the real turn outstanding. And the browser, never having been sent the stored rotation, was recomputing whose turn it was from scratch on every render and contradicting the server. A test now asserts that browser and server pick the same speaker from the same inputs, rather than assuming it.
A Paused Room Stays Paused
Pause used to stop the turn chain, not the chat: every message you sent still drew a reply, and Nudge and Skip quietly cleared the pause before running. A paused chat now advances only when you ask it to. A message you send is saved in full — attachments, dice, inline queries — and draws no reply; the first one in each pause says so, so the silence is not mistaken for a failure. Nudge and Skip each run exactly one turn and stop. Resume resumes. Along the way, the Nudge flag had been dropped between the button and the server, so a nudged character could still pass; and Continue in the all-LLM pause dialog cleared nothing and requested nothing, so the button meant to restart a runaway room only closed the dialog.
The Room Says What It Is Doing
Pressing Enter while a reply was still streaming used to do nothing whatever — no notice, no flash, no log line — which is exactly what a broken send looks like. It now answers: One moment — the room is still speaking. Your remark waits in the composer.
Regenerate reports itself, too. The message being replaced dims under a Regenerating… plate, the new line streams in token by token rather than arriving whole half a minute later, the status strip names the stage as it does for a first turn, and the composer and the message’s controls are held for the duration. When it finishes, the swipe group shows the variant just generated — it used to keep whichever was selected before, so you could watch a new line arrive and then be shown a different one.
The Concierge, Rebuilt
The Concierge has carried two jobs since he arrived: guessing in advance which chats were spicy, and doing something useful when a provider refused. 4.9 gave the first job a fourth state so that a verdict and a vouch would stop sharing a button. Living with it for a month made the larger problem plain. The advance guess is a cheap model’s opinion; the refusal is a fact. 4.10 rebuilds the Concierge around the fact.
Every Refusal, Rerouted
There is now one refusal classifier, and it trusts evidence in order: a typed error from the plugin, a known provider code, a moderation finish reason, refusal wording in the error, and — only on content the Concierge had already flagged — an empty body. A bare 400, a rate limit, or an authentication failure is never a refusal. Five plugins were taught to raise a typed ModerationRejectionError (from the new @quilltap/plugin-types 2.8.0) and to report the real finish reason on both streamed and non-streamed paths.
There is one way to find the uncensored understudy — the configured uncensored profile, then any profile ticked Uncensored-compatible, then nobody — so pre-flight and after-the-fact routing finally agree. And there is one image failover path, used by generate_image, the Lantern’s story backgrounds, Aurora’s avatars, and the manual dialog alike. A thrown content-policy error on a text turn used to be classed as “not a fallback trigger,” and nothing happened; it is now recorded on the route trail as a refusal, retried on the understudy, and then handed to the profile’s own fallback chain.
The Concierge speaks when this happens: a bubble saying a refusal was rerouted, that nobody was available to take it, or that the chat’s state does not permit it.
Three States, Not Four
Under the hood, 4.9’s four states were answering a question nobody needed to ask — who decided — at the cost of the one that matters, which is where the chat goes when a provider refuses. There are three now:
- Moderated — the default. Ordinary providers first; a refusal goes to the uncensored desk; and the Concierge may move the chat on his own.
- Unmoderated — the uncensored desk only.
- Locked — ordinary providers only. A refusal stands.
Who set Unmoderated, you or the Concierge, is provenance rather than a state: it appears in the tooltip and helper text and nowhere else. The header pill and the list mark are red for Unmoderated, grey with a shield for Locked, and absent for Moderated; Quick-hide’s Dangerous Chats hides Unmoderated chats.
The Refusal Ledger
Each chat now counts its moderation refusals. Only real evidence counts — an inferred refusal never does — and once the count reaches a threshold (two, by default; zero means never) on a Moderated chat, the Concierge moves it to Unmoderated and says so, naming the count and the last provider to refuse. He never moves a Locked chat, never moves an Unmoderated one back, and re-reads the chat before he acts, so he cannot overwrite a choice you made a moment earlier. Returning a chat to Moderated clears its ledger.
A Tab of His Own
The Concierge has his own Settings tab now, with five cards: On duty, The uncensored desk (the text, image, vision, and image-prompt profiles, gathered in one place from the three tabs they used to be scattered across), When a provider refuses, Display, and Pre-screening. The global Off / Detect Only / Auto-Route mode is retired, because every question it was used to answer now has a name of its own.
On a new installation the Concierge is on duty with failover and no classifier at all: the per-message pre-screen, the summary classifier, and the ten-minute sweep run only if you switch them on. Off duty means no failover, no announcements, no auto-switch and no pre-screen; the per-chat selector is disabled with a pointer to the tab, and flagged content is shown plainly. A New chats start as setting chooses the state for a chat whose creator does not.
Try Uncensored
Sometimes you disagree with the provider. Every character line now carries a shield in its action bar — Try uncensored — which regenerates that reply as a new swipe on the uncensored understudy, skipping every profile already on the original’s trail and anything running the same provider and model. The same button sits on generate_image results and on the Lantern’s refused-backdrop bubble. It is your request rather than the Concierge’s, so it works off duty, never changes the chat’s state, and is hidden only on Locked chats.
Two smaller courtesies arrived with it. Marking a message Not Dangerous now actually lifts its blur, leaving the chips struck through. And when the Lantern is refused a backdrop and nobody can take it, he says so in the chat rather than failing silently in the job queue.
The state is also, finally, written safely. A whole-row write — a title change, a telemetry update — could previously rewind a newer Concierge state; the state now has a single compare-and-set writer, the classifier no longer flips chats from the background job at all, and every failover path re-reads the chat when a refusal arrives, so a chat locked mid-request is never rerouted.
Every Model Tried
The Route Trail
An assistant message now keeps its route trail: every connection profile asked for the turn, in order, and why each one stepped aside. It renders as a list under the avatar, first asked at the top and the one that answered at the bottom. A row that fell over on its own — a timeout, a rate limit, a dead key, a missing model, an empty reply — is struck through and marked ❌; a row that refused on content is struck through and marked 🚫; and the row that answered has no mark, so a reply that went through first time looks exactly as it always did. Hovering a row names the profile, the provider, the model, how it came to be asked — primary, retry, Concierge, understudy, tier pick — and what became of it. Image results from generate_image, the Lantern, and Aurora carry trails too, and the tool result now names the model that actually painted the picture rather than the one first asked.
The trail is stored only when something failed, which is nearly never, and it rides the .qtap export with its message. Older messages have none and render as before.
A Provider That Goes Silent
Creating a chat could hang forever at Setting the opening scene… behind a dialog that cannot be dismissed. The provider had accepted the request, sent its headers, and then never sent a byte — and every timeout in the stack, by design, stopped at the headers. Provider streams are now watched for silence: a generous budget for the first chunk, since a long prompt with extended thinking legitimately takes minutes to start, and a tighter one between chunks. A silent stream mid-chat fails over to the profile’s understudy like any other network failure; a silent greeting gives up on that provider and opens the chat with its scripted greeting. The budget applies to each gap, never to the total, and reasoning chunks count, so a long answer is never cut off for being long.
The Transcript Arrives Wherever You Are
Until now, a reply reached an open Salon by exactly one route: the stream belonging to the request that tab had made. If that stream was gone — you tabbed away during a long generation, the machine slept, the connection dropped — the reply was saved perfectly and never appeared, and nothing was logged, because a write to a closed stream is swallowed without complaint. The same gap is why Aurora’s wardrobe notes, the Lantern’s backdrops, the Commonplace whispers, and Suparṇā’s mail turned up only if they happened to be enqueued into a live stream at the right moment.
Every message write now publishes on the realtime channel 4.9 built, and every open tab on that chat re-reads its transcript. The re-read is the authority; the stream still carries tokens for the turn in flight, but it no longer decides what the room contains. A dropped stream now costs a typing animation rather than a turn. The re-read is conditional — a version counter on the chat lets the server answer “unchanged” without sending anything — so one busy turn’s flurry of notices costs round trips, not transcripts. Swipe selections and scroll position survive the refetch.
The same lesson was applied twice more. A chat setting changed in Settings never reached a Salon already open in another tab, because the tabbed workspace keeps a hidden pane mounted indefinitely and the Salon had read its settings once, at mount; every settings read now goes through the shared query that every tab observes. And the sidebar’s Delete Memories count, also read once at mount, sat at zero over a chat holding dozens of memories written since — and the button absorbed the click silently because it believed there was nothing to delete. Memories now have a realtime topic of their own, the button is disabled at a genuine zero, and the confirmation re-reads the count before it quotes it.
Pictures
The Chat Gallery
The Gallery button in a chat’s Organize drawer — which, it turns out, had never once appeared, its count having been read from an action the server did not have — now opens a grid of every image in the conversation, whatever produced it: uploads and library links, generate_image output, either Generate Image dialog, every story background the Lantern ever painted, every Aurora repaint, pictures re-shown from an album, files the Librarian attached, the cast’s standing portraits, and images referenced only by a Markdown link in the prose. There is a filter chip per source, a current badge on whatever is in play right now, and Save, Download, and Delete on hover — Delete only where the chat itself minted the image, and never on a portrait, a kept photo, or anything currently on display. The count rides the realtime channel, so a backdrop landing from a background job updates it with no poll.
Image downloads now ask the server for an attachment rather than fetching bytes into the page first, so in the desktop shell a 4K backdrop streams straight to disk.
Avatars, Cached by Outfit
A character wearing the same outfit no longer costs a fresh image every time. Everything that goes into an avatar — the prompt, the provider, the profile, the model, the LoRAs, the options, the size — is hashed into a key stored on the image, and the avatar job checks it first: a match whose bytes still exist is reused, and the Concierge call, the image call, and the file write are all skipped. Put the coat back on and the same portrait comes back. Regenerate Avatar is now a reroll: it bypasses the cache, paints a new one, and makes that the portrait the outfit returns from then on.
Generated avatars are now always stored in the character’s vault, project chat or not. Characters added to a chat already in progress get their outfit avatar on arrival, as the opening cast always did.
A character’s Photo Gallery gains an Avatar Rolls section listing every portrait the cache holds for her, with Set as avatar, Keep in the photo album, Download, and Delete — and deleting a roll you had already kept leaves the kept photo alone. Those rolls no longer clutter the album itself.
A migration brings existing avatars into the cache, and it is the one change in this release you can see without looking for it: duplicate generations of the same prompt are collapsed to the newest, and every chat that displayed one of the others now displays the survivor. On the instance it was measured against, 1,786 avatar rows became 889, and about 141 MB came back. See Upgrading.
What the Model Is Shown
After a few avatars or backdrops had been generated, the next turn on a vision-capable profile could fail outright with 413 Request Entity Too Large from the provider’s front door. Image bytes are not tokens, and nothing was counting them. Images sent to a model are now trimmed to what a model needs to read them — 1,024 pixels on the long edge, 500 KB each, 2 MB across a turn — which took one real avatar from 2.3 MB to 98 KB with the portrait perfectly legible. Stored images are untouched; only the copy on the wire is reduced.
Photos attached from a vault failed on every Z.AI vision profile, and would have on NanoGPT: the host was handing over a server-relative path beside the bytes, and both plugins preferred the path. They now send the bytes, and forward a URL only when it is a real one.
describe_image on a generated picture used to answer with its label — “Story background for: Tuesday in the Garden” — because that is what the generating jobs had been writing into the description field. The jobs no longer write it, the tool now prefers the generation prompt, and a migration clears the labels already on disk.
GPT Image 2.5, and the Rest of the OpenAI Knobs
Two new OpenAI image models arrive: gpt-image-2.5-flare, faster and cheaper at GPT Image 2 quality, and gpt-image-2.5-sunburst, slower and dearer and better at detailed editing. With them come four parameters the plugin had never sent: Quality (up to xhigh and max on the 2.5 pair), Background — transparent cuts the subject out, which is what an avatar wants — Output format and compression, and Moderation (auto or the less restrictive low; not an off switch). The newer models also take arbitrary resolutions within OpenAI’s limits. The OpenAI plugin now builds its image-profile editor per model, so no model is ever offered a knob it would reject.
Three older defects went with it: the manual image dialog’s Generate button posted to a route that did not exist; the generate_image tool’s own schema defaults were overriding your profile’s size, quality, and style on every call; and its size argument had never done anything at all, because a default orientation was injected on top of it.
Smaller Courtesies
Quick-hide gained a Salon Images toggle that hides the backdrop, avatars, thumbnails, and inline pictures in the Salon, for the moments when the screen is more visible than you would like. Import from Image in the wardrobe now offers to make an outfit from the pieces it found, as well as the pieces themselves. The Wardrobe dialog gained a Show shared tickbox beside Show archived, so dressing a character from her own clothes no longer means scrolling past every shared garment in the house. And story backgrounds stopped re-dressing characters the concealment prompt was supposed to drape: the appearance sanitizer had been substituting “casual clothes” for an undressed state before the prompt crafter ever saw it, so a sanitized character arrived already clothed and was painted into a different scene.
Letters, Rosters, and Vaults
The Post Office
Characters without System Transparency — which is to say most of them, by default — could list and send mail but not read it: the instructions pointed at the vault path, and the opacity covenant refuses that path to them. A new read_mail tool reads a letter by its file name from the caller’s own mailbox, transparency notwithstanding, and discard_mail throws one away through the same deletion chokepoint every other file delete uses. list_email is renamed list_mail, to match.
The Project Roster Means Something
A project’s character roster has been stored and displayed for some time without anything ever consulting it. It now governs exactly two things: a character’s tool access to the project’s files, and to the project tier of the shared wardrobe. Chat membership, project instructions, automatic knowledge retrieval, and clothes already being worn are unaffected. Creating a chat, or moving one into a project, no longer adds its participants to the roster behind your back. New projects default to Allow Any Character; the Characters card gained an Add character picker and visible remove buttons for when they do not; and the card no longer reports “0 characters in roster” after any unrelated setting change.
What an Opaque Character Can See
Two linked defects in the document tools. A character with System Transparency off could not reach any group document store — not her group’s, not one linked to it — because hiding her vault had been done by dropping her identity from the request, and group membership is derived from exactly that identity. Meanwhile the same character’s directory listings cheerfully named every character vault the setting exists to hide, and then refused to open them. Listing and opening now consult one rule: vaults are hidden by name, identity stays, and group, project, and General stores resolve as they always should have. A store that exists but is out of reach now says so, rather than claiming “not found” — which a model reads as a typo, and in the reported case spent eight minutes and ten tool calls respelling a name it had right the first time.
A Lighter House
A Quarter Less Disk
Three storage changes, measured end to end on a 2.0 GB reference instance that came out at 1.48 GB afterwards. Images in document stores are now normalized on write at a single point no path can skip — eight write paths had been skipping it — and a migration re-encodes what slipped through, 191.8 MB on the reference instance with dimensions unchanged. LLM log payloads, the same prompt scaffolding re-serialized on every call and the single largest thing in the instance, are compressed. So are the rendered conversation transcripts the Scriptorium keeps. Imports and archive rehydration still restore bytes exactly as archived.
Message Search Is an Index
Global message search was a LIKE '%…%' scan of every message in the instance — 355 MB read per query — and that scan was the one thing preventing the largest text in the database from being stored compressed. An FTS5 index replaces it, maintained by database triggers so no write path can bypass it, and the usual search now takes a fraction of a millisecond. Searches with punctuation work: the old path turned a period into a wildcard, so Mr. Smith silently found nothing. Search now matches whole words and word beginnings — walk finds walking but no longer sidewalk — with accent and full Unicode case folding; a query of nothing but punctuation and single letters, like C++, falls back to the old scan. Results are still the hundred newest.
With the scan gone, message text is stored brotli-compressed. It is lossless: what you read, export, and back up is byte for byte what it always was. On the reference instance the messages table goes from 515 MB to a projected 332 MB.
Old Conversations Keep Their Memory
The stale-chat sweep had been clearing the embeddings of every conversation idle for more than thirty days, which on one real instance removed 85% of its conversation history from semantic search to save about fourteen megabytes. It no longer does. The rendered transcript that every chat row carried around — about 160 KB each, on every chat list — is dropped and rendered from the messages when actually needed, and read_conversation stops failing on chats the old sweep had collapsed. The one cache the sweep had never cleared, compiled identity stacks, is now on its list.
quilltap sync
npx quilltap sync <store> <path> keeps a database-backed document store and a directory in step, in both directions. Edit a file in your own editor and the next run carries it into the store; edit it in the Scriptorium and the next run carries it out. The side that changed wins; when both changed it is reported as a conflict and nothing happens until you say --prefer store or --prefer disk. Deletions propagate only when the manifest the verb keeps in the directory proves the file was there last time, so a first run never deletes anything. Dotfiles are invisible to it. A binary’s caption travels as a .description.md sidecar beside it. Bytes are preserved exactly — a PNG pushed from disk stays a PNG. It never touches a chunk or an embedding; the store’s own hooks re-index. --dry-run plans without changing anything, and the exit codes are designed for scripts.
The CLI’s reference documentation, which had been split between the package README and a developer document, is now one complete reference in the package README.
Under the Floorboards
A Hostname Is Not an Identity
On a Mac, Quilltap could shut down its own database mid-evening. The instance lock’s heartbeat checked once a minute that it still owned the lock by comparing the recorded hostname with the current one — and macOS, when no hostname has been set explicitly, which is the default, derives one on the fly and changes it on Wi-Fi reconnects, sleep and wake, VPN changes, and DHCP renewals. The heartbeat read MacBook-Pro.local becoming Mac as another process seizing the database, closed its connections, and exited. Under the desktop shell the window stayed open over a dead server, with an already-open Salon still drawing from its cache, which made it look like a display problem. Ownership is now a snapshot of process and acquisition time; the hostname is a label. The shutdown that does happen on a genuine loss now runs the same orderly teardown as a normal stop, where previously it threw before closing anything.
An Unknown Action Is a 400
Every API route that took an ?action= parameter decided for itself what to do with one it did not recognise, and several decided badly. DELETE /api/v1/projects/[id]?action= with any unknown action deleted the project. The same was true of groups, and an unknown action on the restore endpoint ran a full restore. Every route now goes through one dispatcher with one rule: no action runs the plain verb, a known action runs its handler, and anything else is a 400 listing the actions that exist.
Boot Knows What It Has
A failed but resumable migration — the avatar-roll collapse, for one — used to stop the server on every boot. Migrations can now be marked resumable; a resumable failure is logged, deferred, and retried next time, and only it and its dependants wait. And once a structural migration had run, nothing at boot ever looked at its table again, so a damaged document-index table made every store read come back empty behind a perfectly healthy /api/health. Every dedicated-database table is now checked at boot, and a problem is reported by the health endpoint as a degraded structure service rather than as nothing at all.
Help That Indexes
Help pages over the embedding provider’s input limit had been silently dropping out of help search, and a section-chunk mismatch in the background job could roll back an entire batch of embeddings. A page’s vector is now the mean of its section vectors, a failed section no longer sinks the page, help is reconciled against the files on disk at every startup, and a test now fails the build on any help section too long to embed. The chat-settings help page, the worst offender, is split in three.
PDFs Read Again
PDF sources given to Summon From Lore or the character wizard had been skipped with only a warning since the PDF library changed its API, and packaged builds — Docker and the desktop tarball — could not extract text from any PDF at all. Both are fixed.
Repairs
Bugs 126 through 178 were filed and closed across the cycle, most of them found by using Quilltap for an evening rather than by any test. The larger ones have their own sections above. A representative handful of the rest:
The star on Aurora’s System Prompts tab that sets a character’s default prompt reported success and did nothing, and underneath it the default was recorded in two places that each update path kept only one of — so new chats could open with the old prompt even when the badge said otherwise. A description saved on a vault image could land on a different file with identical bytes, and the cached caption logic made the same mistake in reverse, re-running the vision model on every attach. Re-uploading an image over itself erased its description. Overwriting a document left the old revision’s chunks answering searches indefinitely. A story background was queued only when the checkpoint retitled a chat, so past turn ten the backdrop stayed on the first scene; and the summary fold overwrote titles you had set by hand. Collapsing a duplicate avatar roll deleted the photo you had kept from it — caught and fixed before release. quilltap db raw SQL could neither read nor write compressed columns, and now decodes them for display. A narrow Salon pane’s sidebar closed any dialog it opened on the first click. Switching the “speaking as” seat sent the wrong verb. The native-binding heal that runs after a Node upgrade reported success while rebuilding nothing. A delete that removed no rows was counted as a deletion and announced to every open tab.
The release’s refactor pass consolidated the nine document-index repositories and the log repository onto a single base class, removed about a thousand lines of dead database code, and fixed two repositories that had been missing from the background-job write partitioner, where a buffered write would have been committed to the wrong database.
Upgrading
Twenty-one migrations run on first boot, and three of them take noticeable time on a large instance. compress-chat-message-text-v1, compress-llm-log-payloads-v1, and compress-conversation-chunk-content-v1 rewrite their tables compressed, create-chat-message-fts-v1 builds the search index first, and recompress-oversized-mount-blobs-v1 re-encodes oversized images. Rewriting rows frees pages inside the database files without shrinking the files themselves; run npx quilltap db optimize afterwards to get the disk back. The rest add columns and tables (the route trail, the cycle order, the transcript counter, informs, the refusal ledger, the Concierge’s state and settings, the avatar cache key) or clear known bad data (scenarios seeded as summaries, labels stored as image descriptions). The avatar-roll collapse is resumable: if it fails, the server starts anyway and tries again next boot.
Duplicate avatar generations are collapsed, and this cannot be undone. The duplicates were not copies of one image but separate paintings of the same prompt; the newest in each group survives, and every chat or override that pointed at another now points at it. Any roll you had kept in a character’s photo album keeps its photo.
The Concierge moves to three states. Existing chats are translated: Uncensored becomes Unmoderated set by you; Vouched Safe becomes Locked; Flagged becomes Unmoderated set by the Concierge; Monitored becomes Moderated. The global mode is retired: Off becomes off duty, and Detect Only and Auto-Route both become on duty with pre-screening and summary classification switched on, so nothing you were relying on stops. One behaviour changes: a Detect Only installation now gets refusal failover, since that is now simply what being on duty means. An Off installation that has any Unmoderated chat is kept on duty, with pre-screening off, so those chats keep their desk. The same translation runs when you restore a pre-4.10 backup. The API’s old four state names now return 400, as do the retired Dangerous Content settings fields; the help page is now the-concierge.md.
Check the roster on any project with Allow Any Character off. The roster is now enforced for project files and the project wardrobe. Chat creation used to add participants to it automatically, so most rosters already hold the characters who have actually been in the project’s chats — but a character who reached the project’s files some other way will now be refused until you add her.
list_email is now list_mail, and the old name is not aliased. If a custom prompt or Pascal tool refers to it by name, update it.
Message search behaves a little differently. It matches whole words and word beginnings rather than any run of letters, so a search that relied on matching inside a word will find less. Punctuated searches that used to return nothing now work.
Old conversations are re-embedded once. On the first boot, every chat the old sweep had cold-tiered is queued to render and re-embed. On a large instance that is thousands of chunks in the background; it costs embedding calls and nothing else.
The impersonation setting becomes three-way. If you had restate in the character’s voice switched on, it becomes Ask each time; otherwise Never.
Summaries that already invented a name are not repaired automatically. Use Rebuild Summary… in the Organize drawer where you see one.
Built-in sample prompts are refreshed in place. Your installation will pick up the revised samples on the next read. Prompts you have imported into a character are copies, and are not changed — copy the new text across if you want it.
The character photo album no longer lists avatar history. Those images are in the new Avatar Rolls section on the same page. Generated avatars are now always stored in the character’s vault, so project-context avatars no longer appear in the project’s file tree; a character with a broken vault now gets no avatar at all.
Provider prompt caches are not rolled. The cache structure version stays at 4: informs, progressions, and the scene note all ride in the uncached tail, and a seat with no subprompts produces a byte-identical system block.
Node.js 24+ is still required, unchanged from 4.9.
Installation
Electron Desktop App
Download the latest .dmg (macOS), .exe (Windows), or .AppImage (Linux) from the quilltap-shell releases page. The window is only ever a front end, and it offers three back ends: Direct (the Node server inside Electron, no isolation), Docker (the container image behind a native window — the recommended arrangement, and the strongest boundary we ship), and Remote (any Quilltap URL, including a dev server, an npx quilltap instance, or a machine on your network). Requires shell 4.2.0 or later.
npm (Node 24 required)
npm install -g quilltap
quilltap
Open http://localhost:3000 in your browser. Requires Node.js 24+. First run downloads and caches locally.
Docker
docker pull foundry9/quilltap:4.10.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 has a stage manager now, and it is you. A note can be passed from the wings to one actor and not the others, and she will believe it over her own diary. A character can be handed instructions for this one performance and have them gone the next night; she can carry a clock that she hears tick every time she speaks. Before the curtain rises the Host will go and look at the place, come back, and describe it to you — without presuming to say who will be standing in it.
The running order is drawn once and posted where everyone can read it, and the house keeps to it. A paused room holds its breath until you let it go. When a reply took three understudies to land, the programme says so, with the names of everyone who could not go on.
The Concierge has stopped trying to guess what a provider will object to and started attending to what it actually says. There are three doors now rather than four, the keys hang in a room of their own, and if you think the provider was wrong, there is a button for that.
And the cast has been reminded, gently and in writing, whose story it is. Your narration is what happened. An agreement is something you said, not something you failed to object to. A proposal left unanswered is remembered as a proposal. Nobody takes a vote on what you are allowed to do.
4.9 made the house notice things. 4.10 lets you direct them.
— Ariadne, for the Bureau, October 7, 2026