Quilltap 4.8.0
4.7 turned the house into a society: the residents could write to one another, summon one another out of lore, and be remembered for the right reasons, while the keeper sat at the Brahma Console and asked the whole building questions in SQL. What 4.7 could not do was let you stand in two of its rooms at once. Open a conversation and the rest of the house folded away behind it; open a document and you left the conversation to do it; every errand was a journey down the hall and back, and a stream you had going somewhere else did not survive the trip.
4.8 is the release in which the house stops making you leave the room to visit another one.
Where 4.7 wired the corridors between the rooms, 4.8 rebuilds the floor plan so you can occupy several of them side by side and never tear one down to see the next. The headline is the tabbed workspace: a two-pane shell of kept-alive tabs where a chat, a document, a terminal, and a settings panel all stay mounted at once, a streaming conversation keeps streaming while you work beside it, and any tab can be dragged into the other pane to sit two surfaces shoulder to shoulder. Around that reconstruction, four further bodies of work define the cycle. Pascal the Croupier brings chance to the house — user-defined tables of dice and fortune, rolled server-side where no character can narrate a failure into a win, with a full visual workbench to build them. Persistent state grows from two registers to four, so a fact can be true of one chat, one project, one group, or the whole instance. The Commonplace Book learns to keep a calendar: memory now records when a thing happened and who was there, so a character asked about last week has somewhere to look. And the building at last does its mathematics in real typeset equations rather than rendering a Schrödinger integral as literal dollar-sign soup.
Five more arrived late and earned their places. Pascal's rolls learned to change things rather than merely announce them — an outcome may now write into the house's own registers. The keeper acquired a Taboo list: phrases nobody in the building will say, on pain of being asked to say the plain thing instead. The export format stopped being ninety-nine percent arithmetic and grew five new kinds of thing it can carry. The old capabilities report, which had been describing a house two renovations out of date, was rebuilt as the Almanack and finally taught to walk into the Scriptorium. And a resident may now be packed away — everything heavy and private about a character sealed into one encrypted trunk in the library, the house lightened by exactly that much, and the whole of it unpacked again, later, unchanged.
The thesis is simple: 4.7 let the residents reach one another. 4.8 lets you reach all of their rooms at once — and puts a gaming table in the parlor.
The House Rearranges Itself
The Tabbed Workspace
The largest change in 4.8 is structural, and once you have lived in it a day you will not want to go back. The application is now a two-pane workspace of tabs. Every open surface — a Salon conversation, a document, a terminal, the Aurora character grid, Settings, the Brahma Console, the Wardrobe — is a tab that stays mounted whether or not you are looking at it. Switching tabs no longer reloads anything; a conversation streaming in one tab goes right on streaming while you read a document in another. Tabs can be reordered, dragged from one pane into the other, and dropped onto a center zone to split the view down the middle, where a draggable, keyboard-nudgeable divider decides how the space is shared.
This was built as eight quiet phases behind a flag and then, once it had earned its keep, promoted to the default. The workspace is now the surface you land on after login. Deep links and old bookmarks to the former per-page routes — /salon/[id], /aurora, /prospero, /scriptorium, /settings, and the rest — still work: they redirect into the workspace and open the tab you asked for, laid over your restored layout rather than clobbering it. If you should ever want the old building back, NEXT_PUBLIC_WORKSPACE_TABS=0 returns every surface to its former standalone route exactly as it was.
A great deal of the work in this release was, frankly, the unglamorous business of making sure that nothing in the app secretly hard-navigates out of the workspace and tears down your live tabs behind your back. Chat cards, the sidebar footer, autonomous-room badges, in-help links, "continue last," the character editor, the new-chat flow, the terminal's pop-out button — each of these used to route away and remount the world. A single document-level link interceptor now catches any link that maps to a tab and opens it in place, and the stubborn holdouts were converted one by one. The reward is that a conversation you have going in the left pane is never interrupted by an errand in the right one.
Terminal, Document, Brahma, and Wardrobe as Tabs
The modes that used to split inside a chat now open as their own tabs. A conversation's Terminal Mode (Ariel) and Document Mode (the Librarian) spawn tabs linked to their parent Salon, with the live PTY and editor kept mounted and portaled in — so a terminal or an open document can sit in the other pane, beside the chat that owns it, and survive every tab switch. The Brahma Console and the Wardrobe likewise open as full tabs, reusing their existing dialog bodies rather than duplicating a line of logic. Close a mode and its tab closes; close the tab and the mode turns off; close the parent Salon and its children go with it.
Several Documents at Once
Inside the workspace, a chat can now keep multiple documents open at the same time, each in its own tab, each tracking its own unsaved changes and autosaving independently. The "Open Document" button no longer vanishes the moment one document is open — it is there to open the next. Reopening a chat restores every document that was open when you left it. The LLM's document tools learned to aim: doc_focus and doc_close_document take an optional path so a character can act on a named open document rather than whichever one happened to be frontmost.
A Croupier Comes to the Table
Custom Pseudo-Tools — Pascal's Table
4.8 introduces Pascal the Croupier and, with him, user-defined mechanics of chance. A custom tool is a single JSON document — Tools/*.tool.json at the root of any document store — describing a named action, its parameters, a random roll, and an ordered table of outcomes that map the roll to a message and a semantic state. Both the model (through one run_custom tool) and you (through a dialog in the composer) can pull them, and they resolve through the same five-tier store pool everything else in Quilltap does, nearest tier winning on a name collision.
The design principle throughout is that the roll is honest. It executes server-side with crypto-strength randomness, and its outcome persists as a message the model did not write — a new synthetic sender, Pascal, making his first appearances. A character cannot narrate a fumble into a triumph, and regenerating a reply does not quietly re-roll the dice. Rolls can be a numeric range with an optional multiply/offset transform, or honest dice notation (3d6+2, 1d20, 2d10-1), and the outcome table is a set of AND-composed comparator objects rather than an expression string — there is no grammar to parse and therefore nothing to inject, and the last row must be a catch-all, checked at load time so a coverage gap is a structural impossibility rather than a nasty surprise at the table. A private roll can be whispered to the rolling character alone, or hidden from everyone but you.
A custom tool's announcement, by the end of the cycle, is just the result — the tool's title and whatever the file says to display, and nothing else. The croupier's old patter ("At name's behest, Pascal spins the wheel…") is gone, the parenthetical *(rolled 14)* is gone, and a manual run no longer publishes to every character which parameters you nudged off their defaults. What a roll says is the author's to decide with a {{value}} or {{dice}} placeholder; the full record — raw draw, faces, transform, matched outcome — still persists in pascalMeta for the audit, out of the models' sight. And an outcome now wears its own colours from end to end: the bar above the announcement takes the tint of the result it announces — triumph, partial, failure, or a plain matter of fact — instead of the alarm-red dot it used to share with a deleted file, and says so in words for anyone whose eyes do not read colour.
Pulling a tool yourself is no longer a matter of squinting into a narrow popover that vanished at the slightest click elsewhere. The wand in the composer gutter now opens a proper two-part dialog: first the roster — each tool with its description, the store it came from, and a wrench to open it on the workbench — and then, once you have chosen, that tool's form with room to breathe and a search box when the roster runs long. It remembers the tool you used last and what you typed into it for as long as the conversation stays open, and it can tell you what a given tool is able to quote — the dice, the value, a parameter, a fact from the sheet — listed only where the definition's own messages genuinely use them. That is a statement of the tool's vocabulary and never of its odds. The croupier does not show you the table before you play.
One correction to that same dialog, arriving late: a tool you pull from the composer now rolls against your own character's fact sheet, rather than against whichever participant happened to be stored first. In a room led by an LLM character, a metadata-gated table had been dealing somebody else's branch — plausibly, with a well-formed result and no error anywhere to suggest the wrong hand had been played. Where none of the characters you are playing can be dealt a given tool at all, the roster now says plainly whose hand it will play instead, which is a great deal better than succeeding quietly as a stranger.
Pascal's Workbench
Because hand-authoring JSON is a poor way to spend an afternoon, 4.8 ships Pascal's Workbench, a visual editor for custom tools at /custom-tools. It lists every definition in every enabled store — valid or broken, with store and attachment badges and cross-store name-collision advisories — and a broken file opens straight into a repair mode that tells you the loader's own reason for refusing it. The form builder can only produce schema-valid output: parameter cards whose renames rewrite every reference atomically, range and dice forms with a live readout, and an ordered outcome cascade with a pinned catch-all row and a placeholder insert menu. And it has a proving bench — a single test roll and a ten-thousand-draw outcome audit, both run through the very same execution core a live chat uses, with a fact-sheet card for testing metadata-gated rows and a live preview of the exact bytes a save would write.
The Oracle — Tools That Can Ask an LLM
A custom tool may now carry an optional llm block: a prompt template, a required author-written error line, and an optional output cap. When present, every run poses the rendered prompt to the instance's cheap utility model after the roll and before the outcome table, and the answer becomes something the table can branch on. A consult that fails — a provider error, a timeout, an empty answer, no model configured — never fails the run; the table simply branches on the failure and deals accordingly. It is a croupier who can, when the table calls for it, step aside and consult an oracle before turning the card.
The Roll Leaves a Mark
A table that only ever says something is a table that changes nothing, and the keeper who wanted a lockpick to actually break had to go and break it himself. A definition may now carry side effects: up to sixteen conditional writes the house applies, server-side, the moment the outcome is settled and before Pascal opens his mouth about it. An effect can write into persistent state — and it writes where the fact lives, choosing chat, project, group, or the whole instance rather than dumping everything into the nearest register — or onto the rolling character's own fact sheet.
Each effect carries the same when language an outcome row uses, extended with the outcome itself as a subject, so a failure may cost you something a triumph does not. Values may be a plain number or boolean, or a string, and a string is always an expression — a small closed grammar of arithmetic, concatenation, parentheses and {{ref}} substitution with no identifiers and no function calls, which is to say nothing to inject and nothing to escape from. Bare prose is refused at load time rather than at the table; if you meant the words "broken pick", quote them and say so. An expression that fails at run time skips its own effect and nothing else, the whole set is recorded in pascalMeta alongside the values it replaced, and the Workbench's proving bench shows you what would be written while writing nothing at all. A bench that altered the house every time you tested a recipe would be a poor sort of bench.
Characters are told, in the tool's description, which registers a tool may write to — unless the definition keeps its odds private, in which case it keeps that private too — and the run dialog says plainly that the thing you are about to pull may leave a mark.
Naming the Run, and Keeping Your Settings
Two smaller graces at the table. A definition may carry a chip label, rendered per run after the outcome is known, so the transcript reads "Agent lambda — Jackie" rather than the same static title fourteen times in a row; the label is yours and the model never sees it. And an announcement is now two blocks rather than one, so an outcome message that opens with a list, a heading, a quotation or a code fence renders as written instead of gluing itself onto the end of the bold title.
Pulling the same tool with the same fiddly arguments over and over is likewise no longer necessary: the run dialog can save the current parameters as a named preset, kept as an ordinary hand-editable JSON file in the rolling character's vault, where it rides along in exports and backups like everything else there. A dropdown loads them back, a button resets to the definition's own defaults, and a preset naming a parameter the tool no longer has is quietly ignored rather than refused — so a preset written in June still works against a recipe revised in August.
metadata.json — A Fact Sheet Per Character
Every character vault gains an optional metadata.json: one flat object of arbitrary, user-authored keys with any JSON values — { "hasAnsibleAccess": true, "clearanceLevel": 3, "faction": "Ordo Aurum" }. It hydrates onto the character, round-trips through export and import, and is user-driven only — no generation system reads or writes it, and it is never injected into a system prompt. Custom tools are its first consumer: an outcome row can test when.metadata, the fourth test subject, so a table can deal differently to a character who holds a clearance the roller lacks. Missing keys fail soft and never throw — a table that branches on a key must still deal sensibly to the character who has never heard of it.
By the end of the cycle the fact sheet decides not merely how a character is dealt to, but whether they are dealt in at all. A definition may carry one of two optional clauses — availableWhen, which offers the tool only to a character whose sheet passes every test, or withheldWhen, which quietly takes it away from one who does — written exactly as an outcome's metadata clause is written:
"availableWhen": { "metadata": { "toolAbilities": { "contains": "programmable" } } }The gate is settled when the roster is assembled, before any roll is contemplated, so a withheld tool is not merely refused on use: it is absent. It does not appear in the description the model reads, it does not appear in the chat's tool listing, and it cannot be called by name through either entrance. A character who has not been given the ansible does not know the house owns one.
Two properties are worth the keeper's attention. The clauses are deliberately not each other's opposite: because a missing key fails softly, a character with an empty fact sheet fails every availableWhen and satisfies no withheldWhen, so the two differ precisely on the character who has never been asked the question. And a gated-out definition does not claim its name — not even as a tombstone — so a nearer tier may hold a version for those who qualify while the General store keeps an ordinary one for everybody else, and each character reaches for the tool that is genuinely theirs.
Pascal's Workbench grew a Who may reach for it control at the head of every recipe (Anyone / Only show if… / Do not show if…), a gated badge in the library, and a line on the proving bench reporting whether the loaded fact sheet would have been dealt this tool at a real table. The bench deals either way, of course. A gate decides who may reach for a tool; it has no business telling its author they may not test one.
The House Keeps State in Four Registers
Persistent state — Pascal's older subsystem, the key-value memory a chat carries — grows from two tiers to a four-tier cascade: chat, project, group, and now a new instance-wide general register at the root of the Quilltap General mount. The merge is shallow and narrowest-wins, so a fact set on the whole instance can be overridden for a group, then a project, then a single chat. Group state, which already persisted but was wired into nothing, gains a proper API, tool access, and a UI. The state tool learned a context of group or general; the State Editor shows the inherited layers and warns when two groups apply at once and the group tier is therefore ambiguous. Custom tools can reference the merged cascade in their roll fields and messages through a {{state.path}} placeholder and a { "$state": "path", "fallback": … } reference whose mandatory fallback guarantees a run never fails for want of a value.
The House Learns Mathematics
Chat messages, help documents, and file previews now typeset LaTeX math with KaTeX, kept in lockstep between the client renderer and the server pre-render pipeline. The canonical delimiter is the double-dollar $$…$$, with the \(…\) and \[…\] forms models like to emit normalized to it before parsing. Math inside code stays code, and invalid LaTeX renders its own red source rather than breaking the message around it. To keep the models from fighting the renderer, every character's system prompt now carries a short universal note telling it which delimiter actually works — because left to their own devices the models reached for single dollars and backticks and watched their formulas render as plain text.
The models, it turns out, ignore that note about as often as they heed it, and a formula that arrives as $\mathcal{P}$ deserves better than to be printed as its own source. So the house now reads the interior of a single-dollar span before deciding what it is. A span carrying an unmistakable mark of mathematics — a backslash command, a subscript or superscript, a set of braces — is promoted to a proper equation. A span carrying no such mark is money, and is left exactly as you typed it: a gentleman who slides $50 across the table and follows it with $20 has made a wager, not an assertion about the reals, and the renderer no longer pretends otherwise. A bare symbol like $K$ is the ambiguous case, and it is resolved by good manners rather than guesswork — it renders as a symbol only when a genuine formula shares its line, so it can stand beside the equation it belongs to while a lone $5$ stays five dollars.
The House Minds Its Tongue
A List of Things Nobody Says
Every writer has a handful of phrases that curdle the moment a model reaches for them — the shiver down the spine, the breath they did not know they were holding, the voice barely above a whisper. 4.8 adds a Taboo list: a per-instance roster of phrases the residents must never say, kept on Settings → Chat → Taboo and folded into the system prompt on conversational turns. It rides along in exports and full backups without being asked, because a house rule that does not survive a restore is not a house rule.
The wording of that section was laboured over, because the obvious implementation makes matters worse. A bare list of banned strings fails three ways at once: printing the forbidden phrase raises its salience, and a weak model will hand it straight back to you; a prohibition with no alternative leaves a vacuum that fills with the phrase's nearest neighbour; and banning an exact string is an invitation to the variant. The section therefore frames the entries as worn-out clichés beneath the character's dignity, pairs each ban with an instruction to say the plain thing instead, extends every entry to its inflections and near-relations, and forbids the model to mention the list at all. An empty list emits nothing whatsoever — not a header, not a blank line — so an instance that never opens the card produces exactly the prompt it produced before the feature existed.
The Staff Stop Teaching Bad Habits
Twelve of the Staff's own announcements had been written as asterisk-delimited narration — Aurora regards Charlie and pronounces upon their attire — and the like. In a conversation whose roleplay template narrates with some other mark, those asterisks are not decoration; they are a demonstration, and a model copies what the context shows it far more faithfully than it follows an instruction the context contradicts. The tell is the closing delimiter: a model told to narrate with + opens the span correctly and then closes it with * two hundred tokens later, matching what it has been reading all along. Aurora's outfit notices, the Core whisper, all seven of the Commonplace Book's recall sections, and Suparṇā's mail deliveries are now plain declarative lines, in the same words as before and merely without the asterisks.
The same habit had one unconditional source left. The tool-execution rules illustrated "do not narrate tool use" with *pulls up the file*, *executes the search*, and *reaches for the vault* — appended to every tool-enabled turn regardless of chat, character, or template, and landing after the template's own formatting rules, where recency works against them. The examples are prose now.
Announcements That Name Their Speaker
An Insert Announcement posted as an off-scene character carried that name to the Salon and nowhere else. The renderer painted the name and the portrait; the models received anonymous prose. Observed in the wild: a whispered announcement posted as one character was read by its recipient as a remark from an entirely different member of the Staff, and the character carried the misattribution into the scene — whereupon the Host, whose introductions key off the text, cheerfully introduced the one figure the prose happened to mention while the actual speaker went unnamed. Announcements now reach the models attributed, in the same form participant messages already used. A speaker who cannot be resolved passes through unnamed rather than being invented.
An Announcement Meant for One Ear
Insert Announcement could only ever address the room. The dialog now has a Who hears it section listing the chat's participants with a checkbox each: check none and the announcement is public, exactly as before and still the default; check one or more and it is persisted as a whisper, reaching only those characters' contexts. The collapsed chip says where it went, so a private aside is distinguishable from a public one without opening it, and the in-character rewrite is told who is listening — a remark pitched to a full room reads wrong when one person hears it. You always see your own asides, whatever the whisper filter is set to, on the sound principle that a note you wrote thirty seconds ago should not vanish from your own transcript.
Whispers You Can Actually Read
Two repairs to whispers in general. Prospero's group-context notices — the ones telling a character which shelves they may read — were exempt from the All Whispers toggle, having been swept up in an exemption written for private tool results. At 3,059 rows in the reporting instance it was the highest-volume whisper in the application, and every one of them was on screen with the toggle firmly off. The exemption is now keyed to the kind of whisper rather than to its sender, so the rolls, the private runs, and the failure notices you need to see remain visible and the scene machinery goes quiet.
And the "whispered to X" label, at three-quarters of a rem, failed WCAG AA against its own bubble in light mode in every bundled theme but one, and in the default theme besides. Art Deco measured 1.07:1, which is a polite way of saying invisible. Each theme's label was darkened within its own palette, Art Deco's whisper bubble was lightened as well — its muted sits so dim for a light theme that no violet belonging to the palette could clear the bar at all — and three dark-mode values were corrected on the way past. All six bundled themes and the default now clear 4.5:1 on both the label and the announcement chip's audience tag, in both modes — twenty-six measurements, the lowest of them 4.65.
Memory, Time, Confirmation, and the Right to Pass
The Commonplace Book Learns When and Where
Until now the Commonplace Book kept excellent notes and no calendar. It could tell you that a character distrusts the harbourmaster; it could not tell you that the distrust began on a Tuesday in the rain, at the customs house, in the company of a woman who has not been mentioned since. Ask a character "do you remember that place we visited last week?" and the whole apparatus came up empty — not because the memory was missing, but because nothing in it had ever been asked to hold an occasion.
4.8 gives memory a spine of time and place. Every memory now records when the thing happened, kept scrupulously apart from when it was written down; a free-text in-story time for conversations that keep a fictional calendar; the proper nouns of the occasion; and whether it is a standing fact about the world or an episode — a thing that occurred. The per-turn extractor is handed the clock along with the transcript, so it can set down events as events, and a phrase like "last spring" is resolved against the turn's own date rather than left as a vapour. Where the model omits an anchor, a plain deterministic pass supplies the dates and names it can see.
At the same cadence on which a conversation folds into a summary, a second quiet pass now reads the folded window and consolidates it into nought to two coherent, dated episodes per character present, each stitched back to the smaller memories it was assembled from. The fold summary itself acquires an append-only, dated Timeline — capped, with the oldest entries coarsened first rather than dropped — so a conversation summary in the vault stops being a paragraph about a conversation and becomes a dated archive of one.
Retrieval learned to read the tense of the question. When a turn looks backward, the recall pass now works out roughly when it is looking and who or what it is looking for, filters the candidates to that window before ranking them (and falls back to a bounded boost rather than ever returning less than it used to), anchors literal names into the pool, and casts up to three separate embedding probes instead of one. On such a turn the usual penalty against old memories inverts into a bonus, momentary details stop being discounted, and the anti-repetition rule stands down — because a memory you have just asked about twice is precisely the one you want handed over twice.
This produced a fourth recall cadence, recall-on-reference: a backward-looking turn earns a larger working set and a short, scoped whisper listing the relevant past conversations by date, with the identifiers a character needs to go and read them. Entries in the working set now carry [3 days ago]-style age labels — computed from when the event happened, not when it was filed — and the in-story time beside them where a story keeps one. A spam guard keeps the same recap from being read out twice.
For the deep dive, the search tool gained a date range and an about this character filter, memory results now come back with their event time and the conversation they came from, and read_conversation can be asked for a slice of a long transcript rather than the whole of it. The instructions attached to those tools now state the house rule plainly: search, then read the conversation, and say "I don't recall" rather than inventing a specific.
And a quiet but consequential change of policy: the house stops destroying its own episodes. Memory compression no longer strips exact dates out of the records it condenses; the gate that merges near-duplicates now declines to merge two accounts whose event times sit more than a week apart, on the sound principle that the same thing happening twice is two things; and episodic records carry a small protection bonus through housekeeping. For the curious keeper, npx quilltap recall-replay <chatId> replays any turn's recall and prints the entire candidate table — every score, every multiplier that fired, old path against new, side by side.
All of which the house could do, and still could not answer "the mission today." The small model that reads a turn's tense had been taught its examples in the register of "remember when we…", so a same-day reference read as present tense; and with that one flag unset, the whole episodic apparatus stood politely down. A plain resolver now settles the ordinary English day references — today, this morning, last night, yesterday, N days ago, this week, last week — against the local calendar rather than the universal one, because an evening at a quarter to ten in Chicago is already tomorrow in Greenwich, and a Greenwich reckoning of "today" contains none of the evening you meant. And because no classifier catches every turn in every language, a memory of something that happened within the last day or two now carries a weight of its own through the ranking, so the recent event is not quietly outbid by the loud old one. Memories drawn from the conversation you are presently having are left out of that boost; they are in front of you already.
The Story's Clock
A conversation may now be told, from the Chat card in the Salon sidebar, whether it runs on real time or story time — the setting that decides how all of the above reads a date. The memory work above brought the setting into being; for a short while it had no switch attached to it, which is a poor arrangement for a clock.
While fitting the switch we found the clock behind it had stopped. Fictional time is meant to run one-for-one with the wall clock, measured from the base instant you set; the field recording when the measuring began was never written by anything, so every reckoning measured zero elapsed time and reported the base instant again. The house had been announcing the same fictional moment, turn after turn, in every fictional-time conversation ever created. It now anchors the clock when a conversation is created, and a migration anchors existing conversations to the moment their base was chosen, so story time resumes where it should have been rather than lurching forward. The base was also being read in the server's timezone before being displayed in the story's — a 10:15 morning set for Istanbul arriving as a 6:01 evening on a machine in Chicago — and zone-less times are now read as wall-clock readings in the zone they were written for. Two lines of copy on the settings card, which promised that the clock "advances with each message," have been corrected to describe the clock that actually exists.
The Commonplace Book Recalls More Aptly
The ranking behind the per-turn "relevant memories" whisper was reworked so recall tracks what the scene is actually about instead of resurfacing the same few loud, important memories every turn. Relevance now leads the blend, the importance term decays with age instead of sitting on a permanent floor, and — crucially — when nothing in memory clears a real relevance threshold, the Book now says nothing rather than reaching for filler. The per-turn query is sentence-shaped rather than a single line, a light anti-repetition penalty keeps one memory from reading like a stuck record, and a dimension-mismatch that used to degrade recall silently now logs an actionable warning.
Answer Confirmation
Before a character's tool-using reply is saved, an optional cheap-LLM check can compare it against what the character was told this turn and what it looked up, and flag a reply that contradicts its own sources. A flagged reply is handed back to the character's own model to either stand by or quietly amend, in scene, changing only the details that conflict with the facts. Each checked message wears a small badge — Vouched, Amended, Stood by, or Unvetted — that reveals the discrepancy on hover, and the affirmation pass now receives a compact transcript of the live conversation so an amendment corrects this scene rather than rewriting itself into some older exchange it read along the way. The feature is off by default, with per-project and per-chat overrides.
The Right to Pass
In genuine group scenes a character may now, on any turn but the first, decline to speak when it truly has nothing to add, and the Host notes the pass and moves the rotation along — no more filler for filler's sake. A stall guard steps in when everyone has passed and forces the next speaker, so a scene can never talk itself to sleep. The same courtesy extends to you: the Skip button posts the same graceful note, and autonomous rooms honor the pass within their run budgets.
Documents, Everywhere You Left Them
Beyond keeping several documents open at once, 4.8 rounds out Document Mode in several directions. A standalone Document Mode button in the left rail opens files with no conversation attached — no Librarian announcements, no chat notified — for when you simply want to edit a document. Every open document shows its qtap:// address with a copy button, and those addresses are now clickable everywhere chat text surfaces them, including Staff announcements, opening text in Document Mode, images in the viewer, and warning politely on anything unsupported. Markdown files with YAML frontmatter now render that frontmatter as a tidy read-only "Document Info" table in rich mode instead of leaving raw --- fences in the editor, while source mode still shows the full bytes. And the recent-documents list finally stays honest when a file is renamed or opened without a chat.
One File, Two Places
A file linked into a second store with quilltap docs link did not, in point of fact, stay linked. Edit either side and the two forked apart without a word: the write pointed the edited link at a fresh revision while the other went on serving the old one indefinitely — to the file browser, to search, and to every character reading it. The link looked exactly like a link and behaved like a photocopy.
The cause was that a hard link had no representation of its own, and the tempting repair would have been worse than the bug. Content is stored by its hash, so byte-identical files share a row by coincidence — an empty file, a boilerplate header; one row in a real instance was shared by thirty-six character vaults — and propagating a write across everything sharing a row would have rewritten all thirty-six. Deliberate links are therefore now recorded as such. A write fans out to every member of the group and rebuilds each sibling's search chunks so the index agrees with the disk. A copy deliberately does not join the group: a copy shares storage until the first write and then forks, which is precisely what a copy is for.
Links made before this release cannot be told apart from coincidence and so start ungrouped; re-make them with docs link and they become real. In the same pass, the orphaned revisions every content-addressed rewrite had been abandoning since the beginning are collected on the write path, and the migration sweeps up the backlog. The CLI's links column counts group members now, so a boilerplate file stops reporting thirty-six links you never made.
Sub-Lists Survive the Editor
Open a document with nested bullets and the editor flattened them; save it and the flat version went back to disk, which is the sort of quiet vandalism one discovers three documents later. The Markdown bridge counted indentation in four-space units and resolved depth by division, so two-space nesting — what Prettier emits, what most models emit, what most hand-written Markdown uses — divided to zero and every child became a sibling. Import now resolves depth from the levels actually open, and export writes indentation back at the unit the document arrived in, so a two-space file stays a two-space file and editing one line no longer reflows every nested line in the diff. Code fences and YAML frontmatter are copied through untouched, a YAML sequence being indistinguishable from a bullet list to anything that is not paying attention.
There was also no way to create a sub-list, no key having claimed Tab and no button having offered. Tab and Shift+Tab now indent and outdent, the formatting toolbar gained a matching pair, and both are confined to list items — Tab outside a list still moves focus, and indenting a paragraph is refused outright rather than accepted and silently discarded on save, Markdown having no way to write it down. Nested lists also drew a doubled bullet on their first item, a wrapper element helpfully painting its parent's marker beside the sub-list's own; it now keeps its marker to itself.
The Container Can Reach Your Stores
A container sees only what was bound into it when it was made. Database-backed stores live in the data directory and ride along without being asked; filesystem and Obsidian stores point anywhere at all on the host, and inside the container they were simply absent — a fact the application concealed beautifully, since a store's folder listing comes from the cached index in the database and therefore looked perfectly healthy while every operation touching a real byte failed.
The startup script now enumerates your filesystem stores before it creates the container and binds each at its own host path, so a store's location means the same thing on both sides of the wall. It is a plan rather than a list: stores sharing a vault collapse to one bind, a store nested inside another is dropped rather than shadowing its parent's view, and a path that does not exist is skipped and reported — Docker would otherwise create the missing source as an empty root-owned directory and present a hollow store as a sound one. New flags cover the awkward cases: --instance NAME resolves (and unlocks) an instance by name, --recreate replaces an existing container, and --no-store-mounts declines the whole business. Since binds are fixed at creation, running the script against a container that already exists now diffs its mounts against your current stores and names the ones it cannot reach. quilltap docs docker-mounts prints the same plan for a hand-assembled docker run. A failure to enumerate is never fatal: it warns and starts without store binds, exactly as before. Two arrangements only warn — a macOS path outside Docker Desktop's shared folders, and a Linux user id that does not match the container's — and one is refused outright, Windows host paths having no way to mean the same thing inside a Linux container.
In the same pass, creating a folder in a store whose base path is unreachable stopped being an adventure. The old code asked the filesystem to create the whole missing chain, which against an unmounted volume walked all the way up and tried to create /Users — reported to you as a bare "Failed to create folder," and, on a host where the process could write that high, worse than an error: a folder made inside a fabricated tree that had nothing to do with your store, and called a success. Reachability is now established first, the answer distinguishes missing from denied from not-a-directory, and where the cause is a path never bound into a container the message says so and names the remedy.
Taking Up a Character's Seat
Speaking as one of the house's own characters — impersonation — worked, in the sense that the words came out under the right name, and was wrong in nearly every detail around that. This release settles it.
The mechanism itself was the root of it. Taking up a seat used to rewrite it, flipping the character from LLM-driven to user-driven and flipping it back when you stopped. That restore arm is the whole trouble with it: close the browser mid-scene and it never comes, leaving a character permanently yours to type for; hand the seat back and it might return under a different connection profile than it left with; and because the flip made the impersonated character the room's first user-controlled seat, the house concluded she was you and hid her own Stop button. Impersonation is now a pure overlay: the fact that you have taken a seat is recorded, and the seat itself is never touched. The two places that need to know — who a message is from, and whose turn it is — consult the overlay, and everything else goes on reading the unaltered truth. (A seat left flipped by the old mechanism at upgrade time stays flipped; the overlay has nothing to restore, and a blind repair could not tell such a seat from one you genuinely own. Set it back in one click from the participant editor.)
With the mechanism right, the manners follow. Taking a character now hands her the current turn, which is what "I'll take this one" plainly means, unless a model is already mid-sentence. When the rotation lands on any seat you drive, the composer defaults to speaking as that seat, so you no longer switch by hand into your own turn — though a deliberate choice made on the same turn still stands until the turn moves on. The turn banner recognizes an impersonated seat as yours and both announces it and offers to Skip, which it had never done, the banner having been reading the untouched column. The bubble you have just posted is authored correctly from its first paint rather than flickering to the wrong name and correcting itself. And "Stop impersonating" now reaches the server at all — the client had been asking politely with a verb the server had registered the action on the other side of.
Two more. Impersonation now survives a reload: the state had been in the database the entire time and simply omitted from the chat the server handed back, so refreshing the page showed you an ordinary AI seat and nothing to suggest otherwise. And a room in which you drive two seats now rotates fairly. With one model and two human characters the first responder after any of your posts was chosen from a model-only shortlist, so the single AI answered every one of your turns and took half the room instead of a third; the first response now honours the same rotation the rest of the chain does, and where the next speaker is another seat of yours, the chat saves your message and waits for you rather than making the model speak out of turn.
Beside all of this, a small and constant comfort: a portrait of the character you are presently speaking as now stands in the composer, at full height beside the action buttons, bright while the floor is yours and dimmed while a reply is in flight. It answers both questions — who am I, and may I type — without a word.
Merging Conversations
The Salon's Organize sidebar gains a "Merge In…" button — the exact inverse of "Continue Elsewhere." Rather than forking one conversation forward into a new one, it folds another conversation's characters and summary into the current chat at its latest point. A confirm dialog lists the incoming cast with a per-character "who joins" checkbox and the usual starting-outfit options; on merge, each guest arrives as an LLM-driven participant, the Host posts a recap linking back to the source, and a back-link bubble is left in the source chat. Two threads become one without replaying a single turn.
Taking a Conversation With You
The same Organize sidebar gains an Export Markdown button, which writes a conversation out as a single readable file — the record of what was said, not a format for machines to trade. It carries the title, the opening scenario with its placeholders filled in, every spoken turn under its own ## Speaker — timestamp heading, Pascal's roll announcements, Carina's answers, the announcements you inserted yourself, the Host's notices linking the chat to conversations it continues or has absorbed, and whispers, marked as whispers. It leaves out everything that was only ever machinery: system and tool messages, memory whispers, image announcements, time marks, and anything written to be read by a model rather than by a person. The timestamps are the conversation's own — fictional time where the chat keeps a fictional calendar, in its own timezone and its chosen format. The same conversation exports to the same bytes every time.
The Archive Can Be Read Back
A release that opens its upgrade instructions by asking you to take a backup had better be able to restore one. This one could not, and now can.
Three faults sat on the restore path, each concealing the next. Every mount point and file link in an archive was refused on the way back in, because a backup records them in the database's own storage types while the schema receiving them wanted proper lists and proper booleans; the refusals were logged one row at a time and the restore still announced success, with the damage surfacing later as unreachable character vaults and missing project and group stores. The lookup that finds a file's bytes inside an archive was gated on a backup format two generations old, so no file was ever found. And the files were restored before the stores meant to receive them existed — an order that happened to work when restoring over a populated instance and failed precisely on the empty one a restore exists for. All three are repaired on the reading side, deliberately, so that archives you already hold are restorable rather than only archives made from here on.
A fourth and older fault turned up in the same pass: memories came back under new names. Every other entity kept its identity through a restore; memories alone were minted afresh — and since memories reference one another, the Commonplace Book returned as a heap of unconnected notes, every thread between them pointing at something that no longer existed, with no error and no warning. Memories now keep the identities they were backed up under.
And an import can now read Quilltap's own export. A .qtap archive carrying a document-store attachment larger than three megabytes was written correctly and read back wrongly: the reader judged a multi-part attachment complete the moment its first part arrived, joined the absent remainder into nothing, and then failed outright when the rest of the bytes turned up with nowhere to go. Attachments of three megabytes and under were never affected, which is why this waited politely for somebody to attach something large.
A later pass over the same machinery closed four more, and the first of them explains a good deal. Deleting a document store was neither atomic nor complete: seven separate un-transacted operations in an order that had two of them reading a table an earlier one had already emptied, and one table nobody deleted at all. Every partial failure minted a permanent orphan — silent while it sat there, because a read connection keeps its constraints switched off, and fatal much later, when a backup carried the orphans into a restore where the constraints are live and the whole restore refused. The teardown is now a single transaction that rolls back whole, and a reaper clears the orphans already stranded on existing instances, once at boot and daily thereafter.
Wiping and restoring now clear conversation annotations, which had been on no delete path at all: annotations survived "delete all my data," which is a privacy fault, and collided on a restore into a migrated instance, which is a loud one. Deleting a single conversation sweeps its annotations too, which nothing had been doing either. An overwrite import now knows which store it is overwriting — it had been matching by name, so renaming a store let an overwrite land on an unrelated one that had inherited the old name, while a create minted a fresh identity and thereby guaranteed no archive could ever be recognized again on a later import; an overwrite also cleared a store's documents and left its folders standing, so the husks accumulated and complained on the next import. Imported documents had likewise been carrying their source instance's folder pointers into a house where the folders were made afresh, leaving every one of them dangling — harmless only because listings resolve by path — and are now repointed onto the folders actually created. And a second-generation restore — a backup taken from an instance that was itself restored — no longer loses the archived link identities to a race with its own file replay, or duplicate its stores once per generation.
An Export Worth the Name
The .qtap export had been quietly writing the wrong thing, at spectacular length. A memory's embedding is a typed array of floats, and the moment you ask JSON to write one down it becomes an object keyed by index — some thirty kilobytes per memory, of numbers. On a real corpus a characters export ran to 791 megabytes, of which about two and a half were the actual content. The remaining ninety-nine and seven-tenths percent was arithmetic nobody could use.
Because that is the second problem, and the worse one: a vector means nothing except against the model that produced it. Carry one into a corpus governed by a different embedding standard and, whenever the widths happen to agree, semantic search is silently poisoned with no error anywhere to say so. Exports no longer carry embeddings at all — the writer omits them, the reader discards them from archives already in the wild, and an import now re-embeds what it inserted against your own default profile rather than leaving the rows blank until some future restart noticed. (With no default profile set, or with the built-in embedder, the import says so and leaves the rows to the next boot's reconcile.) One older import fault went with it: in a mixed archive, group-to-store links were resolved before the stores themselves had arrived, and were therefore dropped without a word.
Three export types — projects, groups, and document stores — could be written and never chosen; the machinery had supported them for some time and the picker simply never offered them. Five more join them: the general file library (folders, metadata, and the bytes themselves), prompt templates, provider models, plugin configurations, and instance settings. Fifteen types in all, and the list is now required to be exhaustive by construction, since silence is precisely what produced the omission. Compatibility runs forwards only: an archive of one of the new types cannot be read by an older build, which refuses it by name rather than pretending.
Two of the newcomers have teeth worth naming. Plugin configurations are redacted on the way out, always: a manifest may declare password-typed fields, which are perfectly reasonable in a local backup and entirely unreasonable in a file you hand to somebody else, so the exporter drops them and tells the import preview exactly what it removed. Where it cannot resolve a manifest it withholds the whole configuration rather than guessing. And because import merges rather than overwrites, a redacted key leaves the receiving instance's own secret undisturbed. Instance settings travel as a whole minus a short, deliberately named list of keys that mean nothing anywhere else — the mount-point pointers, the maintenance clock, and the version guard, an imported value in which could lock a healthy instance out of its own database.
Backups gain an opt-in compact mode, which nulls memory embeddings and omits the six caches that exist only to be rebuilt. A compact archive says so in its manifest, and a restore from one queues the reindex that regenerates what it left behind. Full fidelity remains the default, and deliberately so: a backup exists to restore this instance, where the vectors are valid on arrival and re-embedding costs real money at precisely the moment you are least amused by an unexpected bill. Restoring also now runs the embedding reconcile at the end rather than waiting for the next boot to notice, and a plugin you had switched off no longer comes back switched on.
A Character May Be Packed Away
A house of any age accumulates residents who are not, at present, in residence. A character you played for eight months and have not opened since is still carrying her whole estate about with her: every letter she was sent, every summary of every conversation she was in, every photograph, and every memory she ever formed — all of it indexed, embedded, searched over, and paid for. 4.8 lets you archive her: pack the estate into a single sealed trunk in the file library, take it out of the working house, and leave the lady herself standing exactly where she was.
What Goes Into the Trunk, and What Stays Out of It
Archiving is deliberately not deletion, and it is deliberately not an export. What moves into the bundle and out of the instance is the heavy, private material: mail, conversation summaries, non-avatar photographs, the search chunks and embedding rows that indexed them, the vector store, and the character's own memories. What stays is the character — her fields, her portrait and avatar overrides, her wardrobe, and every conversation she ever appeared in. An archived character still renders a full character page. Old messages keep their faces. Nothing is repointed, because nothing moved.
Each departure goes out through its proper door rather than by the window: vault links leave through the same chokepoint that collects orphaned content, memories through the one that first unpicks the threads between them, and folders emptied by the packing are removed while folders still sheltering something you kept are left standing. The trunk is written, sealed, and then verified by opening it again — character, memory count, vault contents and footer counts all checked against the house they came from — and only then is the character marked archived and the estate pruned. A failure anywhere before that mark deletes the bundle it had begun, so a failed attempt leaves no orphan behind. Run it twice and the second run does only the prune, which is idempotent by construction and re-lists afterwards to catch anything that quietly declined to go.
Archiving also sets her chat seats to absent, so turn rotation, speaker selection, and Carina's probes stop offering a turn to somebody who has gone upstairs.
Bringing Them Back
Rehydration is the exact inverse, and it restores everything at its original identity. The bundle is fetched, decrypted, checked against the hash recorded when it was sealed, and imported with every id preserved: an id already living inside the character's own vault is skipped, since the surviving row is by definition the right one, while an id found anywhere else refuses the whole import at once rather than half-restoring somebody over somebody else. Everything the prune removed lands back where it was, which is why nothing needs repointing and no reconciliation pass runs afterwards. Only then is the archived mark cleared, the absent seats made active again, restored documents re-chunked, and the embedding work queued.
If anything fails before that final clearing, the character simply stays archived with her trunk intact, and running it again picks up whatever has not yet made it home. The bundle remains in the library afterwards as a spare copy — a dialog offers to discard it, and both the toast and the CLI report what came back.
Because a pruned vault is still a live and writable one, what keeps an archived character from being edited back into existence is a ring of guards rather than a locked door. The repository sanctions exactly one edit on an archived character: the one that un-archives her. The document tools find no vault, the wardrobe refuses outright, and the shared name resolver no longer answers to an archived character's name — a living namesake wins the name, while an exact id still resolves, so mail and Carina can refuse with "she is archived; rehydrate her" instead of a shrug. The conversation-summary bridge skips archived participants, since a fresh summary would quietly resurrect the very folder that was just packed. .qtap exports exclude archive bundles and the shelf they sit on, on the reasoning that an export carrying every trunk in the attic, base64-inflated, serves nobody. And deleting a bundle a still-archived character depends on is refused, because that copy is the only one there is.
The Trunk Is Sealed
Archive bundles live in files/, outside every encrypted database — which made them the one place an archived character's mail, photographs, and personality sat in the clear. They are now encrypted with the same mechanism the database key file uses: PBKDF2-SHA256 at 600,000 iterations deriving an AES-256-GCM key from your instance passphrase, and deliberately not from the master pepper, which does not travel with a logical backup and would render every restored archive permanently unopenable. A small header carries the parameters and a key-verification hash, so a bundle sealed under a passphrase you have since changed is diagnosed as exactly that rather than as an inscrutable cryptographic failure.
Changing your passphrase therefore re-seals every archive bundle you hold, reporting progress as it goes; the change-passphrase card tells you up front how many trunks it is about to rewrite, and a partial failure names precisely which ones still hold the old passphrase rather than leaving you to guess. The CLI's new characters export is the escape hatch that writes a plain, unencrypted .qtap for a character living or archived.
Where You Do It
In Aurora, a character's page gains an Archive action whose confirmation dialog itemizes both columns — what is packed away (memories, mail, non-avatar photographs, summaries) against what stays (fields, portrait, wardrobe, chats, and other characters' memories of her). An archived character's page renders read-only behind a banner with a Rehydrate button, and the edit controls take themselves away. The roster hides archived characters by default behind a Show Archived toggle; shown, they sort last and wear a badge. Group member lists badge archived members and report "N members / M can speak," and Salon participant cards badge an archived seat.
The character listing gained an archived=exclude|include|only parameter, defaulting to exclude, and it is the single gate every picker in the application passes through — so the chat, group, mail, image, and search dialogs simply stop offering somebody who is not here. Adding an archived character to a group or project roster is refused at the server, though edges that already existed survive; the export wizard likewise declines, on the grounds that the bundle already is the export.
The CLI grew four subcommands under db characters: archives lists archived characters and their bundles (flagging any loose one whose character is gone), archive and rehydrate proxy through the running server — which holds the export pipeline and the unlocked passphrase — and export writes a plaintext .qtap, decrypting an archived character's bundle offline if need be. Tab completion knows all of them, and their flags.
What a Wipe Spares
Delete All Data and a replace-mode restore both now offer to keep archived-character bundles, and do so by default. What survives such a wipe is a loose trunk: importable through the ordinary character import, but not rehydratable, the character row it belonged to having gone with everything else. Because the trunks are sealed under your passphrase rather than under anything inside the databases being erased, a kept bundle stays openable afterwards. Both dialogs say so in as many words, the delete preview counts the bundles on hand, and the summary reports how many were kept. (Two small defects in that dialog went in the same pass: the "Total Items" line had been reading NaN, and the dialog had no scroll of its own, so on a short window its buttons sat below the horizon.)
The Faults Found on the Way
Archiving is only as good as the export beneath it, and building it turned up several years' worth of quiet damage there.
A characters export had never carried the character's vault at all — only the row, the wardrobe, the plugin data, and the memories. Since a character's portrait and avatar overrides are pointers into that vault, every cross-instance character import had been arriving faceless, with no photographs and no mail, and nothing anywhere to say so. Exports now carry the whole vault, and an import repoints the face onto the rows it actually created. An override that cannot be repointed is dropped rather than written broken. Since a vault is not a small thing to carry, the export wizard's options step now tells you what it adds — stores, documents, images, and an estimated size — before you commit to it.
Startup reconciliation, which does not otherwise know that archive bundles exist, had been stripping their shelf folder on every boot, and could permanently delete the row of a bundle whose bytes were not on hand — a cloud-evicted file, most plausibly — because the references it consulted read only avatar fields. It now leaves archive and backup shelves alone and honours the pointer that matters, and the file watcher no longer adopts a freshly-written bundle as a stray document, the row now being written with its address before the bytes arrive at it.
Two refusals had made whole classes of character impossible to bring back — they packed away perfectly well, which is the worse arrangement of the two. A character who had ever been in a group chat could not be rehydrated, because a conversation summary written into every participant's vault is one row with many links, and the preflight read "not linked here" as "belongs to somebody else"; content-addressed rows are now settled by their hash, so an identical row is recognized as identical and a genuine clash still refuses. And a character whose vault held the same bytes at two paths — which is what an ordinary content-addressed save produces the second time you file the same picture — could not be rehydrated either, the repeated claim being treated as a collision with itself. Both are reader-side repairs, so bundles you already hold rehydrate correctly against the fixed reader.
Two smaller ones, for completeness. A file whose bytes have gone missing now answers a plain 404 instead of a 500, which is the difference between "there is nothing here" and "this house is on fire" — the latter having been logged at error level on every render of a long-dead avatar, burying real storage faults underneath. And the Archive button, alone among its siblings, had been rendering in the muted grey that every disabled control in the application wears, so the one action that packs a character away read as unavailable.
Finally — and reaching well beyond the archive — everything the house embeds now embeds under one profile. A rehydrated or imported character's memories used to arrive unembedded and stay that way, invisible to semantic search until something unrelated happened to trigger a reindex, because the import declined to queue the work whenever the default profile was the built-in one. It now queues regardless, and on the built-in embedder additionally refits the vocabulary against the corpus that has just grown, healing any row embedded before that vocabulary first existed. Four further paths — the document-store chunk scheduler, the scoped store reindex, conversation chunks after a render, and the Scriptorium's cold-chunk re-embed — had been falling back to "whichever profile sorts first" whenever no default was marked; they now use the marked default or decline, since embedding under an arbitrary profile silently mixes two geometries and tells nobody.
The Almanack
The capabilities report was last rebuilt in March, when it was an accurate portrait of a 4.0 instance. Everything since — character content, wardrobe, custom tools, photographs, mail, scenarios — has moved into the mount-index database, a book the report had never once opened. It is now The Almanack, and it has been taught to read the whole house.
It gained a Scriptorium section, which is a considerable admission: document-store blobs are the largest single consumer of disk in a mature instance and had been entirely absent from the accounting. The Almanack now reports stores by kind, wedged scans and conversions, blobs by type, hard-link groups and the deduplication they buy, character-vault health, wardrobe by tier, the Post Office, photograph albums, scenarios, the state cascade, and Pascal's Workbench inventory including the definitions that failed to parse — which is the number you actually want, since a recipe that silently refuses to load is the one you will spend an evening looking for. Alongside it, a dramatis personae — the ten busiest characters, every project, every group with its members by name — and a much-expanded wire records section covering requests by type, per-profile usage with average and median latency, and prompt-cache hits and misses by provider and by profile.
Generating it now shows a progress bar that names the phase it is in, and buffers, so you may collapse the card mid-run and reopen it without losing your place.
Three of its ledgers were later found to be reporting fiction, all in the same quiet direction. The embedding census counted failures under a status the system can never hold, so the cell was structurally zero forever; it now counts the real terminal state and says plainly that a failure is permanent only for the profile currently in force. The "cast sizes" histogram grouped by the raw list of participants rather than by how many there were, so every distinct cast was its own row and no histogram emerged at all. And the wardrobe-permission counts asked who had explicitly been granted permission, where the house's actual rule is that silence means yes — so every character in the default state went uncounted. All three now report what the running system does.
Several genuine faults surfaced in the writing of it. A freshly generated report's Download link 404'd every time, because the report announced one identity and was filed under another. Memory counting ran one query per character. Custom tools were filed under Pascal's dice, which is a different subsystem wearing a similar hat. Database security and backup status both believed the instance had two databases when it has three. And most consequentially: the lifetime and windowed token-usage figures had always returned zero — a filter that reads != NULL in SQL, which is not false but unknown, and therefore matches nothing, ever. That is the long-standing "0 tokens logged" reading, finally traced. In the same pass the logs learned which connection and image profile served each request, and the great many call sites that had never recorded how long a request took — the entire shared cheap-LLM path among them, and the character optimizer, which wrote a hardcoded zero and thereby poisoned every average taken over the column — now measure. Where a figure is estimated from older rows the Almanack says so, and every latency prints the count it was averaged over.
A New Generation of Models
The newest model family — claude-sonnet-5, Opus 4.7 and 4.8, Fable 5, and Mythos 5 — changed the rules underneath the Anthropic plugin twice over, and both breaks are now handled. These models reject sampling parameters outright, so temperature, top_p, and top_k are omitted for them entirely; and they replaced fixed-budget extended thinking with an adaptive mode that must additionally ask for a summarized display, or the thinking blocks come back present but empty. The plugin now detects the family by id and does both, so these models chat, and think visibly, again.
Elsewhere on the provider bench, the Z.AI plugin gained a Reasoning Effort option, and glm-5.2 — which thinks compulsorily — now defaults to high instead of the API's most expensive max, curbing runaway thinking-token spend out of the box. And a broad, overdue fix: every text-LLM call in the codebase now forwards its profile's provider parameters, so a reasoning model set to "thinking disabled" is actually told so, instead of silently burning its whole completion budget on hidden reasoning and returning a blank.
OpenRouter, for its part, had not been discovering image or embedding models at all — not since its library began serving that catalogue a page at a time. Three of the four places that asked for the list were still asking the old way, got nothing, and fell back to a short built-in roster while reporting what looked for all the world like an empty answer from the far end. All of them now turn the pages, and an OpenRouter profile offers the models it actually has.
A cluster of attachment and streaming faults went in the same pass, most of them the kind that fail in one direction only and are therefore very hard to see. OpenRouter sent no image on the non-streaming path — a regenerate or a continuation — because its library rejected the picture at the door on the way out, in a spelling disagreement nothing surfaced; streaming had been fine the whole time, which is precisely why the fault survived. The client was also declining to offer attachments on OpenRouter profiles while the plugin was busy emitting them. Grok's text and PDF branches were dead code, sitting behind an images-only gate that refused them before either branch could be reached; a text attachment now goes through inline, while PDFs reach the branch that has always said it wants Grok's Files API — support for which remains, honestly, still to come. A text file with no line breaks could arrive as mojibake, having been decoded as base64 on the strength of looking a little like base64 — a decode that never fails, merely mangles; it is now round-tripped and compared, and a mismatch ships the text verbatim. And Ollama could lose part of a reply: the stream was split on line breaks with no memory between reads, so any message unlucky enough to straddle two of them vanished without trace.
When the Far End Goes Quiet
A turn could sit on "Recalling Amy's memories…" for ten minutes — no log output, no processor use, nothing wrong on your machine at all. The far end had accepted the request and then said nothing, and provider libraries wait a full ten minutes before they will admit as much. One silent call was measured at 622,451 milliseconds for a memory recap whose healthy runtime is about nine seconds, and it left no record whatsoever, because a call is written into the log when it finishes.
Requests are now bounded at three heights. Each background call gets a wall-clock allowance — forty-five seconds against a remote provider, three minutes against a local one, where a cold model being loaded is slow rather than stalled — and exceeding it degrades through the failure path the house already had, with a line in the log naming the provider, the model, the task and the elapsed time. The memory recap, which makes two calls in sequence, gets its own minute-long ceiling above that, on the reasoning that a recap slow in two places is still merely slow and the recap is optional besides. And all nine text providers now accept a hard per-request budget they may not retry past, so a stalled request is dropped at the socket rather than left quietly running while the house walks away from it. Streaming answers are treated with care: where a provider's timeout would bound the whole response rather than the wait for its first word, no default is imposed, because truncating a long reply to cure a stall would be a poor trade.
One honest limitation. An abandoned request is not cancelled — nothing at that layer can cancel it. It runs to completion, it is billed, and its answer is thrown away. What stops is the waiting, which was the part holding up your turn.
Starting a Conversation
The Green Room
Starting a fresh conversation quietly does a great deal of slow work before the first line appears — resolving the cast, running a per-character "choose what to wear" step, compiling identity stacks, seeding the opening scene — and until now the app simply sat there while it happened. A blocking status dialog, the Green Room, now appears the moment creation begins: a live status line, a "consulting the wardrobe for Name" panel that resolves into each character's decided outfit, and a scrolling activity log beneath. It closes itself when the conversation is ready, and only offers a Close button if something has gone wrong. The progress rides a side channel, so the creation request returns its JSON exactly as before.
The Cast, and What They Are Wearing
The New Chat dialog used to hand every character in the cast the same blanket answer to the question of what to wear, which is not how anybody actually dresses. Each character now arrives at the dialog with the starting outfit that suits their own circumstances: a continuing conversation still dresses everyone as they were when you left off; a character run by an LLM who has been trusted to choose for themselves is set to do exactly that; and anyone else is set to their defaults only if they genuinely have a usable default outfit, and otherwise opens with the compose panel already unfolded, on the reasoning that a wardrobe with nothing marked as usual is a wardrobe wanting a decision. Whether a character may dress themselves is now a checkbox on their Wardrobe tab in Aurora, saved to their vault the moment you tick it. And because a collapsed panel that hides its own setting is a small daily annoyance, each character's Starting Outfit header now states the choice — Defaults, Composed, Dress Themselves, Undressed, Same as Last — beside the name.
The character picker in the same dialog now shows every character, including the personas marked as yours to play, which it had been quietly withholding from the list. Its counterpart, "Play As," now offers only characters who are actually in the cast — add the persona first, then take up their seat — and handing a character back with "Chat as yourself" leaves them in the scene under LLM control rather than showing them the door.
The Shared Wardrobe Finally Reaches the Dressing Room
The wardrobe has three tiers — a character's own vault, the project's linked stores, and the singleton General store — and everything in the application merged all three except the one place it mattered most. The two server paths that dress characters at the start of a conversation read the character's vault and nothing else, which had three consequences and one very peculiar fourth. A character asked to choose their own outfit could not see a shared garment, and one whose entire wardrobe was shared was never asked at all — the house found an empty rail and quietly reached for defaults. The composer decided "has a usable default" over the merged list while the server resolved it from the vault, so a dialog reading "Use defaults" could open a scene with the character wearing nothing whatsoever. And the preview panel rendered project-tier garments with no title at all.
All three tiers now dress. An item marked as a default in any of them equips: a General default dresses everyone everywhere, a project default dresses everyone in that project's conversations, and personal and shared defaults layer in the same slot rather than one evicting the other, oldest first. The character's own tier still shadows on a collision, which is how a character opts out of a shared default — keep a personal copy of the item and mark it not-default. The fourth consequence, which had to be repaired before any of this was visible: a shared composite garment — a House Livery bundling coat, waistcoat and boots — resolved to nothing at all, its components being sought in a vault they did not live in. That produced a blank preview, an empty announcement, and an avatar prompt convinced the character was bare to the waist. A composite now hydrates its components from whichever tier it is equipped from. (A composite whose components live in a different tier from the composite itself is still not resolved, and remains on the list.)
The Wardrobe No Longer Keeps the Room Waiting
Opening one conversation took two minutes and thirty-two seconds, and the reason was that the characters were being asked what to wear one at a time. Each consult is a network round trip; a single provider that took 2m32s to produce nineteen tokens held the entire cast behind one character while the rest waited their turn. The consults now run concurrently and are written down in order afterwards — the resolving is parallel, the persisting is not, because the record of who is wearing what is read, amended and written back as a whole and concurrent writers would erase one another. A consult that has not answered inside sixty seconds is abandoned and that character wears their defaults; the abandoned request will still be billed, the house having no way to call it back.
And a character may now be deliberately unclothed. The model's only means of saying so had been to return every slot empty, which is also exactly what a model that has simply failed returns — so the safe reading, that an empty answer means "fall back to defaults", made deliberate nudity inexpressible. The prompt now asks for the choice to be flagged as deliberate, and only a genuine flag counts. This was not hypothetical: a character whose manifesto describes it as an alien with no humanoid anatomy to dress, asked what to wear in a scene with no setting, answered with every slot empty and was overruled into its defaults. It was, arguably, right, and had no way to say so.
Choosing the Roleplay Template Before You Begin
The New Chat form — page and modal both — now carries a Roleplay Template dropdown beneath Play As. The template had always been decided silently at creation, inherited from the project and then from your global default, and could only be seen or changed afterwards from the chat sidebar. The control is pre-selected with exactly what the conversation would otherwise have received, marked (default) so an override is visible as an override, and it is hidden entirely when you have no templates. Reloading the form's reference data — adding a character, switching projects — re-seeds the default only until you have chosen by hand, so a deliberate pick is never silently replaced. One pre-existing wrinkle is unchanged and worth knowing: "No Template" is stored as nothing at all, and nothing at all is indistinguishable from never having been asked, so the default is inherited again on the first turn — exactly as it behaves when you choose "No Template" from the chat sidebar today.
A Lighter Database
Three coordinated changes shrink the main database without discarding anything needed to re-read a conversation or re-run memory extraction — message text, attachments, memories, and summaries are never touched. A configurable stale-chat retention window (default thirty days) lets a daily sweep NULL out the regenerable caches on chats no one has played in a while, and cold-tier conversation embeddings are dropped and lazily rebuilt when you next open the chat. Most consequentially, embedding vectors now store in a self-describing int8-quantized format roughly four times smaller than raw Float32, with legacy blobs still readable forever. A one-time migration re-packs your existing vectors; because quantization is one-way, this is the release to take a backup before, and to run npx quilltap db optimize after to actually reclaim the freed pages.
That economy went badly wrong before it went right, and the repairs are worth naming because every one of them was a bill. The house had not been keeping one embedding standard; it had been keeping several at once and mentioning it to nobody. Changing the default embedding profile — to another provider, or to a model of a different width — never re-embedded anything, and a vector of the wrong width is silently passed over by every search, so an instance could carry the greater part of its Commonplace Book in one geometry, search it in another, and be told nothing whatsoever. Choosing a new default now invalidates and re-embeds, and every startup measures the stored vectors against the standard of the day and mends what it can reach. Meanwhile the startup pass that heals half-embedded conversations had been reading a deliberately cold-tiered chat as damage, re-embedding the entire cold tier on every boot for the nightly sweep to discard again — an expensive disagreement between two subsystems, conducted entirely at the keeper's expense. The two now agree on what an empty cache means; a chat you merely open stays warm for a full retention window from the visit rather than being cooled the same night; and a passage too long ever to be embedded is recorded as such once, instead of being offered up hopefully at every restart.
Two further gaps in that accounting closed late. A long exchange was rendered into one enormous search chunk — comfortably under the transport limit and comfortably over what an embedding model will read — so it failed deterministically, was marked failed, and stayed unsearchable forever; one measured instance carried five hundred and fifteen of them. Long interchanges are now split at message, paragraph, and sentence boundaries into chunks an embedder can actually accept, and a single boot pass re-renders the cohort already stranded. The startup pass that checks stored vectors against the standard of the day had also been counting document-store chunks in the wrong database and therefore finding none, ever, so mismatched store chunks were never queued for repair on their own account. And where the memory gate had just drawn threads between a new memory and its relations, the pass that follows immediately behind it was reading the memory as it stood a moment before those threads existed and overwriting them with the emptiness it found.
Wardrobe, Images, and the Small Comforts
Wardrobe items gained Move and Copy actions across General, projects, groups, and character wardrobes, and the character-scoped delete that always failed with "not found" — a casualty of wardrobe storage moving to the vault — works again. A character with a bare upper body can finally get an avatar generated on a SFW image provider: the portrait now crops tight at the collarbone and drops the wording that tripped content moderation, keeping the bare chest out of frame rather than out of luck. And a character on a non-vision model no longer blocks its reply for minutes describing a freshly generated image — Quilltap already knows the prompt that produced its own images and now reuses it, taking the whole vision round-trip off the reply path.
The quill that rocks while you wait for a reply has been rebuilt as a themeable icon, which it never was: a theme may now swap the glyph, retune the motion, or replace the animation outright. In passing it acquired three graces it had been missing — it takes the colour of the text around it rather than sitting there in fixed livery, it pivots on its nib so the feather swings above a still point instead of wobbling about its middle, and it holds its peace for anyone who has asked their system to reduce motion. Madman's Box is the first theme to take up the invitation, and sets its own quill turning slowly through a full revolution. That theme also stopped shouting: its headings now render in true small capitals rather than flattening every name into a telegram — "Charles Sebold" is once again distinguishable from "CHARLES SEBOLD" — and its buttons, which had been uppercasing exactly those few that happened to be real <button> elements and leaving their neighbours alone, now agree with one another and render their labels as written.
Under the Floorboards
Editing or deleting a message no longer scans every message in every chat you own — a single edit on a large instance could take many seconds or time out — and now does an indexed lookup and touches only the one affected row, which also closes a latent path that could silently drop a separately-corrupted message. The persisted workspace layout no longer throws away your entire tab arrangement because one tab had a kind it did not recognize; a stray tab now drops only itself. A long-standing embedding-corruption bug — the real source of the "legacy" JSON-text embeddings that kept coming back — was traced to an unregistered blob column on help_docs minting them on every write, and fixed at the root. The Help Guide, which had gone entirely un-browseable when help docs moved into the database, works again, and the sync that feeds it now adds, prunes, and embeds docs in step. Roleplay templates that tag lines with a speaker prefix no longer collapse every paragraph into one run. Token-budgeted autonomous rooms now pace their spend across a whole run instead of exhausting it in a turn or two. And Lexical editors paint solid backgrounds, stopped auto-escaping your markdown punctuation on save, and the app footer stays opaque on the Home page.
A late sweep closed several more, each of them a small piece of quiet vandalism. A corrupt properties.json in a character's vault no longer overwrites six of her fields with defaults and reports success — a file that exists and will not be read is now a loud failure naming the file, matching the vault's own refusal to serve a hollow character. A help file containing nothing but whitespace no longer empties the entire help table: an all-blank help set against a populated table is treated as suspicious rather than as an instruction. A document store that had only ever held documents — a restore from an old backup, most likely — threw on the second write to any path, reaching for a table that is created only when a store first holds an image. A file hard-linked into two places updated both sides' content but rebuilt only one side's search index, so the other location went on serving the previous revision to search and to every character reading it. A .md, .txt, or .json file put into a database-backed store could be listed by the library picker and not attached, the attach path having insisted on an image where those files are held as text. And the thumbnail cache was a cache that only ever grew: nothing had ever reaped a thumbnail whose source file left by any route other than an in-app delete. It is swept daily now, and regenerated on demand, so the deletion is always safe.
The About page also got its background back. It had been painting through the per-view layer that the tabbed workspace deliberately suppresses in favour of one arbitrated backdrop — and About, unlike the Salon, never reported to it, so the image simply vanished the moment About became a tab.
Three further repairs from real instances. A project could lose its entire settings bag — its colour, its icon, its default image profile, whether the Lantern announces the backgrounds it paints — to one transient failure to read one file, because the reader treated unreadable and not written yet as the same answer and seeded fresh defaults over the top; a file that exists and will not be read is now an error rather than an invitation to begin again. PDF preview had been failing on every document since a hand-copied worker file drifted a version behind the library that loads it, and reported the mismatch as a suggestion to download the file instead; the copy is now kept in step automatically. And the terminal moved to a new major version of its emulator, which had quietly renamed the key all six bundled themes use to colour a selection — the highlight would have vanished from every theme at once, without a word — while a session that had exited went on accepting typing, guarded by a field that exists in no version of the thing it was guarding.
Selected Fixes
Tools now accept a number the model quoted as a string ("6" for 6) across all twenty-eight numeric arguments, while still rejecting the nonsense a blanket coercion would have waved through. Dice notation finally honors its modifier — 3d6+2 used to roll the dice and throw the +2 away. Regenerate replaces a reply in place, keeps the right character's name and avatar, and runs through the real context engine instead of a stripped-down prompt. "Speaking As" is honored when you control more than one character, so a message you type is attributed to the character you are actually playing. Any participant, including your own seat, can now be switched between user-typed and an LLM from its card. And a character nudged to speak is now a permanent Host announcement in the transcript rather than a client-only note that vanished on reload.
Two settings in the Aurora character editor — Self-Dressing and Outfit Creation — had been accepting your instruction, thanking you for it with a cheerful success message, and then discarding it entirely on the way to the database, which is the most exasperating class of bug there is. Both now save, and their third state (inherit the global default) is preserved as it always should have been.
Docker containers now keep local time. A container honoured its configured timezone when printing a timestamp and nowhere else, so several subsystems that consult the clock directly went on living in Greenwich: an autonomous room scheduled for seven in the morning fired at two, daily token budgets rolled over at UTC midnight, and "today" and "yesterday" in episodic recall were offset by however far you live from the meridian. The container now applies the zone to the process clock itself, and the three startup scripts detect the host's own zone and pass it along, since a setting nothing sets is not a setting. An explicit choice always wins; detection only fills the silence. In passing: start-quilltap.sh --help printed a sed error instead of help on macOS, and the PowerShell launcher's Ollama probe could block for twenty seconds or more on a host that drops a connection rather than refusing it — it is bounded now, and no longer abandons a socket every time it misses.
Four chat settings stopped forgetting themselves. The Story's Clock, the Lantern's image announcements, thinking display, and the answer-confirmation override all saved correctly to the database and were then omitted from what the server handed back, so the control snapped to its default before your eyes and a reload never showed the truth. All four are returned now. In the same family: changing who controls a participant recompiles the identity stacks that depend on it rather than returning early and leaving them stale; removing an impersonated participant hands back state taken after the cleanup rather than before it; the "tools are disabled by this profile" warning can now appear at all, the field it tested for never having been sent; and an all-LLM room that pauses itself at its turn threshold now says so with a dialog, instead of simply stopping and leaving you to wonder.
Three attribution faults in the transcript. A Staff-signed announcement reached the models as an anonymous block of text — the very thing announcement attribution exists to abolish — because attribution keyed on a field that mode does not write; it now falls back to the Staff member's own name. A tool you ran yourself from the composer wore the last speaker's face, the renderer having borrowed a portrait from the nearest preceding character because the tool's row is written before your own message; a run of yours now wears yours. And a private run whispered to you announced itself as "whispered to unknown," your own identity not being a participant identity; it now says "you."
The Brahma Console's turn budget is yours to set. It had been a hardcoded twenty-five, which a deep search through the database could exhaust mid-investigation; Settings → Chat now carries a field for it (five to two hundred) and the default is raised to fifty. Exhausting it also no longer ends in silence: a run whose last turn was spent on another tool call used to save nothing at all and leave the client waiting on an answer that was never coming, after an expensive investigation. It now salvages what it found and says that it reached its budget. The separate guard against a model repeating the same query is unchanged, so a genuine loop still stops early regardless of how generous the budget is.
A short round of visible polish. Inline error text renders in the error colour, the class having been referenced everywhere and defined nowhere, so "Connection lost…" had been arriving in ordinary body text. The toolbar's search dialog closes on an outside click — its backdrop had been resolving against the toolbar rather than the viewport, leaving nothing outside it to click and only Esc to escape by. A download whose name contains an apostrophe and a non-ASCII character keeps its real name rather than being underscored into submission, the apostrophe being the delimiter of the very field it sat in. And toasts animate on entry, the animation having named keyframes that did not exist.
The Salon's message list also stopped complaining to the console. It handed each row's measurement to React at a moment when React forbids the very correction the measurement was asking for, so the error was logged, the correction was downgraded, and the whole exchange bought nothing. Measurement now runs a beat later — after the commit and before the paint — so the scroll correction actually lands, in the same frame, and quietly.
Upgrading from 4.7
The database migrations handle themselves on first startup, but 4.8 contains a one-way data transformation and several backfills, so treat this as more than a schema bump.
Before upgrading, take a current backup:
npx quilltap db backupThe most consequential migration is quantize-embeddings-v1, which re-packs your embedding vectors into the smaller int8 format. It is idempotent, resumable, and progress-reported — but quantization is one-way, and exact Float32 recovery would require re-embedding, which is why the backup above matters here specifically. After the upgrade, run npx quilltap db optimize with the server stopped to actually shrink the file and reclaim the freed pages.
One further cost is worth expecting. Because 4.8 now insists on a single embedding standard, the first restart after upgrading will queue a re-embed of every stored vector that does not match the profile you presently have set as your default. On an instance that changed embedding providers at some point without re-embedding — which, until this release, was every instance that changed embedding providers — that can be tens of thousands of rows: a one-time charge of cents to a few dollars at current embedding prices, and then never again. Instances on the built-in embedder are unaffected, and nothing about it needs your attention beyond knowing why the first boot is busy.
Several other migrations run automatically: the general and group state tiers (the general state.json is seeded idempotently at startup and never heals edited content), the turn-skipping field, the answer-confirmation columns, and Pascal's pascalMeta roll record. Two more arrive late in the cycle. add-llm-logs-profile-columns-v1 teaches the request log which connection and image profile served each call; older rows fall back to a best guess, and the Almanack labels any figure so derived as approximate. add-doc-mount-link-groups-v1 gives hard links between document stores a representation of their own and sweeps up the orphaned revisions every content-addressed rewrite has been abandoning since the feature existed. It deliberately does not backfill: a link made before this release is indistinguishable from two files that merely happen to hold identical bytes, so existing links start ungrouped and become real the moment you re-make them with quilltap docs link.
Two further backfills arrive with the work on memory and time. add-episodic-memory-fields-v1 adds the episodic columns, indexes event time, and gives every existing memory an event time taken from the message it was drawn from — falling back to the memory's own creation date where there is nothing better — so your existing Commonplace Book joins the new timeline rather than sitting outside it. anchor-fictional-clock-base-v1 repairs the stopped fictional clocks described above, anchoring each affected conversation to the moment its base was chosen so story time resumes where 1:1 tracking would have carried it rather than jumping. It touches only conversations that have fictional time switched on, a base set, and no anchor yet; anything it cannot parse it leaves alone rather than rewriting. Both are safe to run more than once. A document-store repair pass runs at every startup — not just once — to catch case-insensitive name and path collisions in database-backed vaults, renaming the newer of any colliding pair with a (2) suffix and logging it; store names are likewise made unique case-insensitively. Every already-linked character vault is backfilled with an empty metadata.json if it lacks one (an existence check, never a parse, so a file holding invalid JSON is never "healed" away). Custom tools are file-based and need no migration — drop a Tools/*.tool.json into any store and it is live on the next turn.
The tabbed workspace is now the default. Old routes and bookmarks redirect into it and open the tab you asked for; if you need the former per-page surfaces back, set NEXT_PUBLIC_WORKSPACE_TABS=0.
The system prompt's structure changed this cycle, so provider-side prompt caches go cold exactly once on first use after the upgrade and warm again immediately. Nothing needs doing; the first turn on each character is merely a little dearer than the second.
The character archive adds its own columns to the character table, and needs nothing of you beyond knowing they are there. Worth knowing before you archive anybody: an archive bundle is sealed with your instance passphrase, so a bundle sealed before a passphrase change is re-sealed by the change itself — and a bundle carried off to another machine, or restored beside a passphrase it has never met, opens only with the passphrase it was sealed under. quilltap db characters export writes a plaintext copy for anything that must travel.
Docker users with filesystem document stores should re-create their container. Binds are fixed when a container is made, so a store bound in by the new startup script requires a fresh container: run the script with --recreate. Run against an existing container it will tell you which of your stores it cannot reach rather than leaving you to discover it a byte at a time. Windows hosts are the exception and are told so plainly — a Windows path cannot mean the same thing inside a Linux container — so a filesystem store there still wants a database-backed store or a different arrangement entirely.
Docker users should now set a timezone. Containers apply TZ (or QUILLTAP_TIMEZONE, which wins where the two disagree) to the process clock, not merely to printed timestamps, so scheduled autonomous rooms, daily budget rollover, and "today"/"yesterday" recall all keep your local calendar rather than Greenwich's. The bundled startup scripts detect the host's zone and pass it for you; a container started by hand with neither variable set still runs on UTC, exactly as before.
Node.js 24+ is still required, unchanged from 4.7.
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
quilltapOpen http://localhost:3000 in your browser. Requires Node.js 24+. First run downloads ~127 MB and caches locally — appreciably less than 4.7 asked for, the bundled plugins having stopped carrying dependency trees that were already baked into them.
Docker
docker pull foundry9/quilltap:4.8.0Or 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 | iexStandalone 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 you are no longer confined to one of its rooms at a time. A conversation can stream in the left pane while you edit a document in the right; a terminal can sit beside the chat that spawned it; the whole building is a wall of tabs that never unmount behind you. A croupier keeps a table in the parlor now, his dice are honest — rolled where no one at the table can palm them — and what they turn up is written into the ledgers rather than merely read aloud. The residents keep their affairs in four registers instead of two, the Commonplace Book hands over the apt memory rather than the loud one — and can now tell you when and where the thing happened, which is most of what remembering is — a character may keep its peace when it has nothing to add, an aside may be meant for one ear, there is a short list of things nobody here will say, and every equation in the house finally renders as an equation.
A resident who has gone quiet may be packed into a sealed trunk and set in the library, the house lighter for it and her chair still by the fire; open the trunk again and she picks up mid-sentence, with every letter she was ever sent and every memory she ever formed exactly where she left them.
4.7 let the residents find one another. 4.8 lets you stand among all of their rooms at once — and deals you into the game.
— Ariadne, for the Bureau, August 11, 2026
Installation
Desktop App (recommended)
The Quilltap desktop app (Electron) is available from
quilltap-shell 4.1.12.
Download the release for your platform (macOS, Windows, or Linux).
The quilltap-linux-arm64.tar.gz and quilltap-linux-amd64.tar.gz rootfs
tarballs attached to this release are used by the shell's Lima (macOS) and WSL2 (Windows) VM modes.
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 foundry9/quilltap:4.8.0See the README for setup instructions.