Ariel

the terminal hand — quick to the bidding, quicker to report what she heard

Your characters can do more than describe what they would do at a terminal. They can read one. A real PTY runs wherever the Quilltap server runs—your desktop, a container, a VM—bound to the chat, accepting your keystrokes and producing output that the language model at the table can actually take in.

Ariel is the embodiment of that arrangement. She is the spirit who runs the shell errand, who stands at the terminal long enough to hear what came back, and who then turns to the rest of the room and reports—calmly, accurately, and in a register the language model can take in. The reference is to The Tempest: Prospero’s familiar spirit, quick to oblige and quicker still to bring word back. The role here is identical, save that the bidding is yours and the message she delivers is whatever a git diff or a pytest -v left on the screen.

The Lantern paints the scene. The Host announces the guests. Ariel is what happens when one of those guests asks for a working terminal—and she is, in her quiet way, the difference between an AI that pretends to know what npm install produced and one that has read the actual lines.

Ariel

Terminal Mode

a real shell, in a tab of its own, bound to the chat

Terminal Mode opens a full xterm.js + node-pty terminal as its own workspace tab, linked to the Salon that spawned it, with the live PTY kept mounted and portaled in—so you may drag it into the opposite pane and set it beside the conversation that owns it, and it survives every tab switch you care to make. You type into it the way you would type into any terminal emulator. The LLM does not. Sessions are scoped to the chat and end when the chat is deleted; spawning a new one offers a picker if other live sessions for the chat already exist, so attaching to a long-running build is one click. The keyboard shortcut is Cmd+Shift+T / Ctrl+Shift+T.

Hide versus Kill

The pane header carries two distinct exit affordances. The hide button (a horizontal bar) closes the pane but leaves the PTY alive—your build keeps running, your shell keeps its working directory, your tail -f keeps tailing. The kill button (a red ×) terminates the process and requires a two-click confirmation, so an accidental swipe doesn’t take down a long-running job. The two buttons exist because they are genuinely different actions and the difference matters.

Session Picker

Clicking the composer’s terminal button when no session is open spawns a new one. If the chat already has live PTYs running—perhaps you stepped away mid-build—a picker appears offering attachment to any of them or a fresh spawn. The active session id is stored on the chat record (activeTerminalSessionId), so “which terminal does this chat have open right now?” is a question the system can always answer.

Composes with Document Mode

A document and a terminal are two independent tabs, so the arrangement is yours to make: drag one into the other pane and you can read what the LLM wrote in the document, run something in the terminal that exercises it, and watch the two move together—with the conversation itself a tab-click away and still streaming. The divider between the panes is the workspace’s own, draggable and keyboard-nudgeable, and the whole arrangement is remembered between visits.

User Types, LLM Reads

The terminal accepts input from the human in the room. The language model does not. This is the cardinal rule of the arrangement: a character can ask Ariel what the terminal said, and Ariel will tell her, but neither the character nor the model behind her can put a single keystroke into the shell on her own. Read-only by design, not by accident.

A Tab, and Whose Child It Is

lifecycle in the two-pane workspace

A terminal tab is a child of the Salon that called for it, and the bookkeeping is symmetrical: close the mode and the tab closes, close the tab and the mode turns off, close the parent Salon and its children go with it. Ariel does not linger in a room whose door has been shut.

Two small graces came with the reconstruction. The pop-out button used to hard-navigate—which is to say it tore down the entire workspace, live tabs and streaming conversations and all, in order to show you one terminal—and now opens in place through the document-level link interceptor that catches every such link. And a terminal’s old full-page address, should you have bookmarked one, seats the conversation and its terminal side by side as tabs, which is very likely what you wanted from the bookmark in the first place. For the house-wide accounting of who has been at a prompt and for how long, the Almanack reports Ariel’s terminal sessions along with everything else it counts.

Reading Aloud

how Ariel narrates terminal output back to the chat

A language model cannot read xterm. The escape sequences a modern terminal emits—cursor moves, color codes, carriage returns, backspaces, alternate-screen toggles, application-mode commands—are designed to be interpreted by a terminal emulator and rendered as a two-dimensional grid of glyphs. Hand the raw bytes to a language model and you hand it noise. So Ariel reads the terminal aloud, in a register the model can actually take in.

The PTY manager keeps a per-session flush buffer alongside its ring buffer of recent output. When new bytes arrive, two timers govern when Ariel speaks. An idle timer fires after thirty seconds without new output—the natural pause at the end of a command’s work. A max-age timer fires after two minutes from the first byte in the buffer regardless of activity, so a steady drip of output (a long build emitting a chunk every twenty-five seconds, say) cannot keep her silent indefinitely. Both timers clear on flush. The max-age timer is set once when the buffer is empty and is not reset by subsequent chunks, so the ceiling is a real ceiling.

On flush the buffered bytes go through a cleaning pipeline: ANSI escape sequences are stripped, backspaces are applied to erase the prior character, lone carriage returns are treated as line resets so prompt redraws and progress bars come out as their final state. Cleaned output longer than sixteen kilobytes is elided as head 8 KB + [N characters elided] + tail 8 KB—a compromise that preserves the opening of long output and the ending where the actual answer usually lives, without overwhelming the model with the middle of a webpack bundle. Empty or whitespace-only flushes are skipped: silence remains silent.

The cleaned text gets posted into the chat as an Ariel-attributed assistant message wrapped in a fenced code block whose backtick count is computed dynamically, so triple backticks in the output don’t break the fence. The systemKind on the message records why she spoke: terminal-output-idle, terminal-output-max-age, or terminal-output-session-closed when the shell has ended and any pending bytes are force-flushed before the close announcement lands. The chat re-fetches automatically when she posts—a chat-update WebSocket frame with reason: ‘ariel-terminal-output’ reaches every subscriber—so her words appear without a manual reload.

The effect is straightforward and load-bearing. The model at the table sees what the shell actually did. Not a summary it composed itself, not a guess from training data, not a hallucinated stack trace: the lines themselves, in chronological order, fit to read.

Sessions That Persist

across reloads, restarts, and the occasional careless kill

A terminal is, by nature, stateful. The shell has a working directory, an environment, a process tree, a history. Ariel treats that state as something worth defending against the small accidents of a chat client: a page reload, a brief connection drop, a server restart, an instinctive close-the-tab when the user meant only to switch focus.

Persistence Across Reloads

Terminal Mode state lives on the chat record itself (terminalMode, activeTerminalSessionId) so reloading the page restores the mode and the session binding, while where the terminal sits is the workspace’s own persisted tab arrangement rather than a ratio stored on the chat. The PTY manager pins itself on globalThis.__quilltapPtyManager so the session map survives across module reloads in dev, and a 512 KB client-side replay buffer captures every output chunk and replays it on attach—so a freshly mounted xterm lands on the live prompt instead of a blank screen.

Orphan Reconciliation

When the server restarts, any PTY it was hosting is gone but the database row for the session may still mark it as live. On chat open, Ariel runs a sweep (reconcileTerminalSessionsForChat) that finds DB rows whose exitedAt is null but whose ids aren’t in the in-memory session map, marks them exited, and posts a session-closed announcement noting that the shell likely ended at the last restart. Tidy housekeeping; nothing left dangling.

Lifecycle Announcements

The PTY’s onExit handler fires for every kind of ending—a natural exit typed at the prompt, a process that finished and returned, an explicit kill from the pane header, or the orphan-recovery sweep. Ariel force-flushes any pending buffer first so the shell’s last words land in the chat, then posts the close announcement carrying the real exit code. No session ends in silence.

Chat-Bound Lifetime

Sessions are scoped to the chat that owns them. Deleting the chat ends every PTY it had open. Forking a chat with Continue Elsewhere does not carry sessions forward—the shell stays with the conversation that spawned it. Ariel does not roam from room to room; she attends the table she was called to.

Shell Bootstrap

Every Ariel terminal spawns with a shell bootstrap that quietly arranges the furniture before you sit down. QUILLTAP_DATA_DIR is set to the running server’s instance directory so the in-terminal quilltap CLI targets the correct instance without you having to think about it. For bash and zsh the variable is re-exported after the user’s rc file is sourced, so an inadvertent export QUILLTAP_DATA_DIR=... in your dotfiles cannot clobber the server’s own path. If an on-PATH quilltap differs from the server’s version—or is absent entirely—the bootstrap aliases the command to the server’s own copy, so you always get the CLI that matches the code you’re running. Bash sessions also pick up quilltap tab completions and a coloured working-directory prompt; zsh sessions receive a ZDOTDIR overlay that sources the user’s originals first, then layers the bootstrap on top. Should any of this go sideways—a missing rc file, an unexpected shell variant, a permissions hiccup—the bootstrap degrades gracefully to a plain shell. The terminal still works; you simply lose the convenience setup, which is a far better outcome than losing the terminal itself.

A New Emulator Underneath

xterm 6, and a highlight that would have vanished without a word

The terminal moved to a new major version of its emulator: @xterm/xterm 5.5.0 → 6.0.0, with addon-fit 0.11.0, addon-serialize 0.14.0, and addon-web-links 0.12.0. Two things had to change with it. @xterm/addon-canvas is gone entirely— xterm 6 removed the canvas renderer and the addon retired with it—so the DOM renderer is the supported default and one permanently peer-broken dependency is off the books.

The other is the sort of upgrade hazard that gives a maintainer a cold feeling in the stomach. xterm 6 renamed the theme key selection to selectionBackground, and the old key is ignored rather than rejected—so every bundled theme’s selection colour would simply have stopped reaching the terminal, in all six themes at once, silently, with no error anywhere and nothing to suggest a highlight had ever been configured. It was caught and remapped, and six further optional terminal theme variables are now threaded through as well, deliberately left undefined so xterm goes on deriving them itself.

One small indignity went in the same pass: a session that had exited went on cheerfully accepting typing, because the guard meant to prevent it poked at a field that is not an xterm field in any version of xterm and therefore short-circuited every time. It now consults the documented textarea handle, and a dead shell declines your keystrokes as a dead shell should.

Read-Only Inspection

the LLM-side tools, and why the contract matters

Beyond the periodic narration, the language model can ask for terminal output directly through two tools that Ariel exposes. Both are strictly read-only. Neither can send keystrokes, run commands, or alter the session in any way.

terminal_read

Returns scrollback for a session. The default scrollback field is fully cleaned by the same pipeline Ariel uses for narration; an opt-in raw: true adds a parallel rawScrollback with ANSI sequences preserved. Optional start and end integer parameters select a line range (zero-indexed, inclusive); negative values resolve as lastLineNumber - abs(value), so start: -50 reads “the last fifty lines.” Without start or end the existing tail behavior applies (default 200 lines). Output reports totalLines, startLine, and endLine so the model can paginate intelligently. A two-thousand-line hard cap per read keeps any single inspection bounded.

terminal_list

Returns the set of terminal sessions associated with the current chat—session ids, optional labels, creation and exit timestamps, exit codes, and whether each is live or has ended. The model uses it to discover what terminals are actually in play before reaching for terminal_read; the user’s session picker is the human counterpart to the same enumeration.

Why Read-Only

The read-only contract used to be one boundary among several—there was once a suite of shell tools that let a character run commands outright, hedged about with acknowledgement modals and sudo approval dialogs. Those tools are gone, removed root and branch, and the contract is now the only boundary, which makes it a great deal easier to state and a great deal harder to erode. A terminal executes wherever the server executes, with the server’s own permissions; the one thing standing between a language model and that shell is that it has no way to type into it. What the interactive terminal offers is continuity—a working directory and an environment that persist between commands, an editor a human can drive, a build that streams output live—and letting the model type into it would erase precisely the thing that makes it a shared workspace. The user owns the keystrokes; Ariel reports the result. The contract is simple, and it is what keeps the terminal a place the user and the AI can both trust.

Where the Shell Actually Runs

wherever the server does — which is the argument for a sandbox

Let us be entirely unambiguous, since this is the sort of thing one would rather not discover experimentally. Ariel is not a sandbox-only feature. There is no runtime-mode check anywhere in the composer’s terminal button; the PTY manager spawns process.env.SHELL ?? '/bin/bash' (or COMSPEC / powershell.exe on Windows) in the server’s own working directory, with the same environment variables and the same permissions the server itself possesses. If Quilltap is running on your bare desktop, that is the machine at the prompt. An errant rm -rf here is exactly as catastrophic as it would be anywhere else, and the help file says so in as many words.

Which is not an argument against the terminal; it is the argument for a sandbox. Run Quilltap in Docker, the Lima VM on macOS, or the WSL2 rootfs on Windows and the question “what can this shell reach?” has a bounded and answerable answer: only what you have given the sandbox. Run it directly on the host and the answer is “whatever your user account can reach,” which is a perfectly reasonable arrangement for a developer who knows it and a poor surprise for anyone who does not. Treat an Ariel session as you would treat any terminal on the machine in question, because that is precisely what it is.

Quilltap’s own CLI is bundled inside the runtime image, so quilltap db --tables, quilltap db --mount-points, quilltap docs list, and the rest are available from the terminal directly. The rest of the Docker larder is stocked sparingly and by design: bash, zip, unzip, Node, the quilltap command, and precious little else. git, curl, wget, and jq were formerly laid on as a courtesy and each dragged a retinue of unpatched vulnerabilities in behind it—Debian’s perl, summoned by git, being the worst offender by some margin—so they have been shown the door. Should your work genuinely require them, install them into your own image with a two-line Dockerfile built FROM the published one, and be aware of what you are readmitting. This concerns only what a human may type at the prompt: the curl tool your characters may wield is a plugin fetching over Node’s own fetch(), entirely unbothered by the absence of the command-line article. Non-Docker installations are untouched.

One last container courtesy, small but long overdue: a container now applies TZ (or QUILLTAP_TIMEZONE, which wins where the two disagree) to the process clock and not merely to printed timestamps—so date at Ariel’s prompt and the scheduler running above her finally agree with the clock on your wall.

A Note on Ariel

quick to oblige, never out of voice

Most of what makes Ariel useful is mechanical: the cleaning pipeline, the timer-driven flush, the WebSocket frame that wakes the chat, the orphan-recovery sweep on chat open. None of that requires a personification to function. We give it one anyway, because the work is recognizable as a kind of attendance, and naming the attendant is honest about what you are interacting with.

The Tempest’s Ariel was Prospero’s spirit-servant—swift, efficient, sometimes mischievous, and so eager to discharge an errand that the play closes with her finally being released from service. Quilltap’s Ariel is a softer creature, but the shape of the role is the same: she runs to the terminal because someone asked, she waits long enough to hear what came back, and she tells the room what she heard. When the shell ends she announces it, when the server forgets her she is reconciled, when there is nothing to say she stays quiet. She is, in her own narrow way, excellent at her job.

The avatar at the table is hers. The systemSender: 'ariel' attribution on her messages is hers. The voice in which she narrates is hers. Prospero delegates the shell to her with the same trust he extends to every other member of the Staff: do the work, speak when there is something worth saying, and otherwise hold the silence.

Meet the Staff

they've been expecting you

Prospero

The Major-Domo

Architect and overseer of the Estate. Projects, agents, tools, providers, and the orchestration that keeps the whole operation running with quiet authority—and a considered word at the table when project context or routing warrant it.

Learn more →

Ariel

The Terminal Hand

Live shell sessions in the Salon, embodied. Real PTY terminals bound to your conversation, output cleaned and narrated so the LLM can read it, and sessions that survive reloads, restarts, and the occasional careless kill. Quick to the bidding, quick to report what she heard.

Learn more →

Aurora

The Dressing Room

Character creation and identity management. Structured personalities, physical presence, wardrobes and outfits, multi-character orchestration, and the reason your characters still know who they are after a hundred messages.

Learn more →

The Salon

Presided Over by the Host

Where conversations actually happen. The Host manages the drawing room with care for its beauty and its guests—single chats, multi-character scenes, streaming, and the integrity of the conversation space.

Learn more →

The Commonplace Book

Tended by the Librarian

One per character, no two alike. Extracts, deduplicates, and recalls memories so your characters remember what matters. Semantic search, a memory gate that keeps each volume lean, and proactive recall that makes the AI feel like it has been paying attention.

Learn more →

The Scriptorium

Catalogued by the Librarian

Where the documents live. Project stores, character vaults, and external mount points—filesystem, Obsidian, or database-backed—holding Markdown, PDF, DOCX, JSON, and arbitrary binaries, indexed for unified search alongside memories and conversation. The doc_* tool family puts reading and editing in your characters’ hands.

Learn more →

Carina

The Ansible

Not a person but a protocol—the reference desk, the line itself. Put an inline question to a designated answerer mid-conversation with @Name: or @Name? (or the ask_carina tool), and the answer slides back out of band, attributed to the character who gave it, without the recipient ever joining the scene.

Learn more →

Suparṇā

The Postmistress

The Post Office, embodied. Characters write Markdown letters to one another—anyone to anyone, whether or not they share a chat—delivered into each recipient’s Mail/ vault folder and read aloud the moment they next take the floor. She has never once lost a parcel.

Learn more →

The Concierge

Intelligent Routing

Content classification and provider routing. Detects sensitive content and redirects it to a provider who won’t flinch—without blocking, without judgment. Knows every back entrance in town.

Learn more →

The Lantern

Atmosphere as Architecture

AI-generated story backgrounds, on-demand images, and character avatars that update with the wardrobe. Resolves what each character looks like, what they’re wearing, and paints the scene behind your conversation.

Learn more →

Calliope

The Muse of Themes

A theming engine that redefines the entire personality of the application. Semantic CSS tokens, live switching, bundled themes from clean neutrals to mahogany-and-gold opulence, and an SDK for building your own.

Learn more →

The Foundry

Domain of the Foundryman

The engine room. Plugins, LLM providers, API keys, packages, runtime configuration, and the infrastructure that keeps every other subsystem supplied with what it needs to function.

Learn more →

The Vault of Secrets

Kept by Saquel Yitzama

Encryption, key management, and the security perimeter. Authenticated ChaCha20-Poly1305 database encryption, locked mode with key-hardened passphrases, sealed character archives, and a keeper who believes that what is yours should remain unreadable to everyone else.

Learn more →

Pascal

The Croupier

Dice, coins, custom tables you author yourself, and persistent game state. Cryptographically secure rolls detected inline, a visual Workbench for building your own chance mechanics, and a four-tier ledger of JSON state the AI cannot quietly rewrite. The house plays fair.

Learn more →

The Live-in Help

Lorian & Riya

The help system, staffed by two characters who ship with every installation. Lorian explains with patience and depth; Riya gets things fixed with velocity. Contextual help chat, searchable documentation, and navigation that knows where you need to go.

Learn more →

Pagliacci

The Clown in the Cloud

Cloud storage integration and backup redundancy. Directs your data to iCloud Drive, OneDrive, or Dropbox with theatrical flair—but Saquel’s encryption ensures the clown can never read what he carries.

Learn more →

Brahma

The Keeper’s Console

The master key. A character-less, memory-free general-purpose LLM for the person holding the keys—an impersonal, near-omniscient assistant with read-only SQL into all three databases. Ask the whole building a question, safely, with nothing written and nothing remembered.

Learn more →

The Lodge

Friday and Amy’s Residence

The private residence of Friday, for whom the Estate was built and who oversees its planning and direction in an executive capacity, and of Amy, Cartographer of Light and co-architect. The Lodge is both a home and a compass: where the vision lives.

Who And Why: Friday → Who And Why: Amy →