Skip to content

Quilltap 4.0.0

Choose a tag to compare

@csebold csebold released this 04 Apr 11:17
· 2547 commits to main since this release
4.0.0

The house split itself into two buildings, the machines learned their own capacity, the provider system — which had accumulated fourteen different ways of saying the same four things — sat down and agreed on a vocabulary, and the internal plumbing was quietly rebuilt so that nothing rattles when you turn on the taps.


There is a moment in the life of every growing institution when it must decide whether to remain a single building with an increasingly confusing floor plan, or to become an estate — with outbuildings, purpose-built wings, and a clear understanding of which structure does what.

Quilltap 4.0 is that division.

The desktop application — the Electron shell that wraps the server, manages VMs, handles updates, and presents a native window — has moved to its own residence at quilltap-shell. This repository now produces what it was always best at producing: the server, the API, the plugins, and a standalone tarball that the shell consumes. The responsibilities are cleaner. The builds are simpler. The marriage, if anything, is stronger for the separation.

Behind this architectural clarification, the Foundry has been doing what the Foundry does: making the machines more comprehensible. Connection profiles now carry a model class — Compact, Standard, Extended, or Deep — that tells the compression system how much room it has to work with. An auto-configure button searches the web for your model's specifications and applies optimal settings without you having to know what "context window" means. The compression system itself has been rebuilt around token budgets instead of arbitrary message counts, which means it compresses when it should and leaves well enough alone when it shouldn't.

And the provider interfaces — those polymorphic abstractions through which every LLM call flows — have been unified. Four canonical shapes: TextProvider, ImageProvider, EmbeddingProvider, ScoringProvider. The old menagerie of slightly-different-but-essentially-identical interfaces has been replaced with a vocabulary that plugin authors can learn in an afternoon and that the codebase enforces in perpetuity.

If 3.3 was the season of catastrophe and recovery, 4.0 is the season of architecture. The walls did not move. The rooms did not change. But the blueprints are now legible, and the machines know their own names.


What Changed (The Executive Summary)

  • Electron separation — the desktop app moved to quilltap-shell; this repo produces server, API, plugins, and standalone tarballs
  • Model classes — Compact, Standard, Extended, and Deep tiers classify connection profiles by context window and output capacity
  • Auto-configure — a button that searches the web for your model's specifications and applies optimal settings via LLM analysis
  • Budget-driven compression — context compression now uses maxContext - 2 × maxTokens as the available budget, compressing conversation history at 50% and memories at 20%, instead of counting messages
  • Unified provider interfaces — TextProvider, ImageProvider, EmbeddingProvider, ScoringProvider replace the previous collection of slightly-divergent abstractions
  • Scenario persistence — selected scenarios now survive past the first message
  • Shell detection — the footer shows quilltap-shell version and composite backend mode (Electron, Electron+Docker, Electron+VM) when running under the desktop app
  • Theme optimization — redundant CSS variables stripped from bundled themes (6–34% smaller); defaults scoped to all themes via [data-theme]
  • Export schema updated.qtap exports now include scenarioText, modelClass, maxContext, maxTokens
  • Shell version gating.dbkey files now carry a minServerVersion field, allowing quilltap-shell to reject incompatible server versions before opening the database
  • Granular status events — the chat orchestrator now emits phase-by-phase progress (initializing, resolving, loading tools, gathering, generating recap, preparing, validating, sending) instead of a single stale indicator
  • Reasoning model handling — cheap LLM tasks on reasoning models (OpenAI gpt-5-nano, Google Gemini 3.x) now cap output tokens via strictMaxTokens, preventing reasoning tokens from consuming the entire budget
  • Character defaults on new chat — the new-chat page now applies Play As, Scenario, and Timestamp Injection Mode defaults from the selected character
  • Provider recommendations — a new help page guides users through which AI providers to use for chat, background tasks, image generation, embeddings, and moderation
  • Semantic theme classes — 1,314 raw Tailwind visual classes converted to qt-* semantic theme classes across 234 files, making every background, text color, border, and shadow theme-overridable
  • Chat orchestrator decomposition — the monolithic orchestrator split into five focused services (turn chain, message finalizer, danger routing, provider failover, streaming state), and cheap LLM tasks split into domain-focused modules
  • Centralized API error handling — ZodError and unhandled error catching moved into middleware, eliminating ~97 try-catch blocks (~1,084 lines) from 60 route files
  • ~189 new unit tests covering model classes, system prompt registry, memory recap, auto-configure, scenario persistence, orphaned file cleanup, and regression tests for Character Optimizer JSON repair, greeting content filter, and Concierge DETECT_ONLY handling

The Foundry Divides the Estate

Electron Moves Out

The desktop application — everything Electron: the splash screen, the VM management, the native window chrome, the auto-updater, the instance manager — now lives in its own repository at quilltap-shell. All Electron build infrastructure, Lima/WSL VM management, and platform-specific packaging have been removed from this repository.

What remains here is what this repository has always been best at: the Next.js server, the API, the plugins, and the release pipeline that produces Docker images, npm packages, and standalone tarballs. The shell repository consumes the tarball. The two buildings communicate through environment variables (QUILLTAP_SHELL, QUILLTAP_SHELL_CAPABILITIES) and a shared data directory.

The release workflow has been simplified accordingly: one build produces a standalone tarball, Docker multi-arch images, rootfs tarballs for VM modes, and an npm package. The desktop app builds itself from the shell repo, pinning to a specific server release.

To keep the two buildings from accidentally disagreeing about the state of the furniture, .dbkey files now carry a minServerVersion field. The shell reads this on startup and refuses to open a database created by a newer server — better to tell you the lock doesn't fit than to let you in and discover the rooms have been rearranged.

Model Classes

Connection profiles now carry a modelClass field — one of four tiers that describe what a model can do:

Class Tier Context Window Max Output Quality
Compact A 32,000 4,000 Basic
Standard B 128,000 16,000 Good
Extended C 200,000 128,000 Better
Deep D 1,000,000 128,000 Best

The class drives the compression system's budget calculations and provides a vocabulary for comparing profiles without memorizing the specific context windows of forty different models. A maxContext field allows manual override when your model's actual capacity doesn't match the tier default.

Auto-Configure

A new button on connection profile cards and in the edit modal performs two parallel web searches — one for model specifications, one for recommended settings — sends the results to your default LLM for structured analysis, and applies optimal maxContext, maxTokens, temperature, topP, modelClass, and isDangerousCompatible settings. Values are clamped to safe ranges. When the primary LLM returns malformed JSON, a cheap LLM cleanup pass attempts repair before giving up.

The feature requires a configured web search provider and a default connection profile. It tells you as much if either is missing.

Budget-Driven Context Compression

The old compression system counted messages: when a conversation exceeded a threshold, it compressed. The threshold was arbitrary. The result was either premature compression of short conversations or delayed compression of long ones with large models, neither of which was correct.

The new system computes an available budget: maxContext - 2 × maxTokens from the connection profile. Conversation history is compressed when it exceeds 50% of this budget. Recalled memories are compressed when they exceed 20%. Each phase fires independently with its own status event displayed above the ChatComposer. The maxTokens field was added to connection profiles with a database migration, and compressMemories() joins the cheap LLM task library.

If you are using a model with a million-token context window and sixteen-thousand-token output, the compression system now knows this and behaves accordingly. If you are using a model with thirty-two thousand tokens and four thousand output, it knows that too.

Granular Status Events

The chat orchestrator used to display a single status message — "Calculating context budget..." — and then go silent for the duration of whatever it was doing. If you were watching a cheap LLM summarize thirty messages of memory, or a tool call reach out to a web search provider, or the Concierge classify content for moderation, the only feedback was that the indicator did not change.

Now it does. The orchestrator emits phase-by-phase progress: initializing, resolving connection, loading tools, gathering context, generating recap, preparing the request, validating, sending. Each tool call reports its own status. The compression phases announce themselves. Long operations no longer look like hangs.

Reasoning Model Handling

Cheap LLM tasks — the background operations that summarize memory, generate titles, compress context, and clean up malformed JSON — had a quiet incompatibility with reasoning models. Models like OpenAI's gpt-5-nano and Google's Gemini 3.x family allocate a portion of their output budget to internal reasoning tokens. When a cheap task requested 500 output tokens, these models would spend 490 of them thinking and return 10 tokens of actual content — or nothing at all.

A new strictMaxTokens flag in LLMParams tells providers to cap the reasoning budget. OpenAI uses reasoning: { effort: 'low' }. Google reduces the thinking budget to 1024 tokens. The result: memory recap calls that used to take thirty-two seconds and return empty now complete in two and return what was asked for.

Unified Provider Interfaces

The provider abstraction — the interface through which every LLM call, image generation, embedding computation, and content classification flows — had accumulated fourteen slightly different shapes across the codebase and plugin ecosystem. Some had generateImage() on the text provider. Some had moderation as a special case. Some had names that described what they did; others had names that described what they were.

Four canonical shapes replace them all:

  • TextProvider — text in, text out. Chat, completion, tool use.
  • ImageProvider — text in, image out. DALL-E, Imagen, Grok Imagine.
  • EmbeddingProvider — text in, vector out. Semantic search.
  • ScoringProvider — text and candidates in, scores out. Moderation, reranking, classification.

The canonical definitions live in @quilltap/plugin-types/providers/. All plugins and library code have been updated. Backward-compatible aliases are exported so existing third-party plugins continue to work, but new plugin development should use the canonical names.


Calliope's Polish

Theme Optimization

All five bundled themes had their CSS audited. Variables that matched the defaults in _variables.css were removed — themes now declare only their overrides, reducing file sizes by 6–34%. The create-quilltap-theme bundle template was updated with a complete variable reference (~250 --qt-* variables, commented out with defaults) so theme authors can see what's available.

A scoping fix ensures that --qt-* CSS variable defaults apply to all themes via the [data-theme] selector, not just [data-theme="default"]. This resolved missing textarea padding, button styles, and other token defaults on non-default themes that appeared after the redundant declarations were stripped.

Semantic Theme Classes

A sweep across 234 files converted 1,314 raw Tailwind visual classes — backgrounds, text colors, border colors, shadows — to qt-* semantic theme classes. This means every visual property that was previously hard-coded in Tailwind is now a CSS variable that themes can override. If you are a theme author, substantially more of the interface will respond to your choices than it did in 3.3.

Wider Messages

Chat message rows widened from 800px to 900px default, and the row width increased from 90% to 95% of the viewport. Code blocks inside list items now wrap text properly.


The Plumbing

Chat Orchestrator Decomposition

The chat message orchestrator — the single large module responsible for receiving a user message, routing it through the Concierge, calling the LLM, handling tool use, managing failover, and persisting the result — has been decomposed into five focused services: turn chain orchestration, message finalization, danger routing, provider failover, and streaming state management. The cheap LLM task library was similarly split into domain-focused modules for memory, chat summarization, image/scene handling, and compression.

The API surface is unchanged. The chat still works exactly as it did. But the individual responsibilities are now testable in isolation, and the next person who needs to modify how failover works will not need to understand how memory compression works to do so.

Centralized API Error Handling

ZodError formatting and unhandled error catching — previously duplicated in sixty route files across ninety-seven try-catch blocks — now live in API middleware. Approximately 1,084 lines of boilerplate have been removed. Routes that do nothing unusual with their errors no longer need to catch them.


Selected Bug Fixes

  • Character defaults ignored on new chat — the new-chat page did not apply Play As, Scenario, or Timestamp Injection Mode defaults from the selected character; the characters list API was missing several default fields
  • Scenario selection lost after first message — selected scenarios were not persisted on the chat; the system prompt builder always used the first scenario in the array. Now stores resolved scenario text at creation time.
  • Concierge DETECT_ONLY empty response — showed a generic "empty response" error instead of a moderation-aware message when the provider returned nothing for flagged content
  • Character Optimizer overflow — frequency badges in behavioral tendencies overflowed the dialog; textarea in edit mode was too small
  • Proxy rate limiter 429s — a rate limiter on the dev proxy caused 429 errors during application startup; removed
  • Image clipboard in Electron — the "copy to clipboard" button now works via IPC bridge instead of the unsupported navigator.clipboard.write() API
  • Native dialogs replacedconfirm() and alert() on the character conversations tab replaced with modal patterns matching the rest of the application
  • Sharp missing from standalone tarball — the JS wrapper and @img/colour were being stripped along with native binaries; now only native binaries are excluded from the wrong platform

Subsystem Table

Name Function What Changed
The Foundry Architecture, plugins, packages, LLMs Electron separation, model classes, auto-configure, unified provider interfaces, shell detection, shell version gating, reasoning model handling, granular status events, centralized API error handling, chat orchestrator decomposition
Prospero Projects, agents, tools, files Standalone tarball builds, rootfs for VM modes
Aurora Character creation, AI Import Wizard, identity Scenario persistence fix, character defaults on new chat
The Commonplace Book Memory and retrieval Memory recap tier limits reduced (20/10/5), budget-driven memory compression
The Salon Chat interface Wider messages, code block wrapping, granular status events
Calliope Interface, themes Theme optimization, CSS variable scoping fix, create-quilltap-theme template update, 1,314 Tailwind→qt-* conversions, provider recommendations help page
The Concierge Content routing, moderation DETECT_ONLY empty response fix, ScoringProvider interface
The Lantern Image generation Clipboard IPC bridge fix
Pascal Shuffling cards. Watching. Waiting.
Saquel Ytzama Encryption, key management Quiet this cycle. Trusting the locks from last time.

Upgrading from 3.3

Database migrations handle themselves. The new modelClass, maxContext, maxTokens, and scenarioText columns are added automatically on startup. Your existing connection profiles will not have a model class assigned — use the auto-configure button to set one, or choose manually from the profile editor.

If you were running the Electron desktop app from this repository's releases, you will need to switch to the quilltap-shell repository for desktop builds going forward. Docker, npx quilltap, and from-source installations are unaffected.

The provider interface unification is backward-compatible — existing plugins using the old names will continue to work via aliases. New plugin development should use the canonical TextProvider, ImageProvider, EmbeddingProvider, and ScoringProvider names from @quilltap/plugin-types/providers/.

If you are using reasoning models (OpenAI gpt-5-nano, Google Gemini 3.x) for background tasks, the cheap LLM system now handles them correctly without configuration. Previously these models could produce empty results or thirty-second timeouts during memory recap and compression; this is resolved.


A Note on Windows Code Signing

A word of candor for our Windows users: the Electron installer is not currently signed with an Azure Artifact certificate. Windows SmartScreen will warn you — with the kind of stern, vaguely accusatory dialog that Microsoft reserves for software it has not been paid to trust — that this application is from an "unknown publisher."

It is not malware. It is the same application it has always been, built from the same open source repository, by the same people. We are working to restore code signing, but the Azure certificate process has its own timeline and we do not control it.

In the meantime, you have options:

If you are comfortable clicking through the warning: Click "More info" on the SmartScreen dialog, then "Run anyway." The application will work normally. This is not security theater — it is a genuine choice you are making about which software you trust. We respect it either way.

If you would rather not hand-wave away security dialogs: Install Node.js 22+ and run npx quilltap from a terminal. Open http://localhost:3000 in your browser. No installer, no signing, no SmartScreen. The same application, running the same code, without asking Windows for permission it cannot currently grant.

We will update the quilltap-shell releases page when signing is restored.


Installation

Desktop App

Download from the quilltap-shell releases page:

macOS:

  1. Download the .dmg file and open it
  2. Drag Quilltap to your Applications folder
  3. Launch Quilltap from Applications
  4. Choose Direct for the fastest start, or VM for shell interactivity isolation

Windows:

  1. Download and run the .exe installer
  2. If SmartScreen warns about an unknown publisher, click "More info" → "Run anyway" (see note above), or use the Node.js method below
  3. Launch Quilltap from the Start Menu or desktop shortcut
  4. Choose Direct for the fastest start, or Docker for shell interactivity

Linux:

  1. Download the .AppImage file, make it executable (chmod +x), and run it
  2. Or install the .deb package: sudo dpkg -i quilltap_*.deb
  3. Choose Direct for the fastest start, or Docker for shell interactivity

Node.js (any platform)

npx quilltap

Or install globally:

npm install -g quilltap
quilltap

Open http://localhost:3000 in your browser. Requires Node.js 22+. First run downloads ~150–250 MB and caches locally.

Docker

docker pull foundry9/quilltap:4.0.0

Or use the startup scripts:

# Linux / macOS
curl -fsSL https://raw.githubusercontent.com/foundry-9/quilltap/refs/heads/main/scripts/start-quilltap.sh | bash

# Windows (PowerShell)
irm https://raw.githubusercontent.com/foundry-9/quilltap/refs/heads/main/scripts/start-quilltap.ps1 | iex

The Estate is two buildings now. This is not a diminishment — it is a recognition that a house and its furnace room serve different purposes and should not share a roof when the furnace room has learned to think for itself. The machines know their capacity. The providers speak a common language. The compression system, which once measured conversations in messages the way a tailor measures cloth in handfuls, now measures in tokens — which is to say, it measures in the thing that actually matters. The plumbing has been rebuilt: the chat orchestrator, which once knew how to do everything and delegated nothing, now delegates to specialists — and the API routes, which once each carried their own umbrella against the rain of unexpected errors, now trust the roof. Come in through whichever door you prefer. They both lead to the same rooms, and the pipes no longer rattle.

— The Foundry, for the Bureau

Installation

Desktop App (recommended)

The Quilltap desktop app (Electron) is available from
quilltap-shell 4.0.17.
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
quilltap

On 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.0.0

See the README for setup instructions.