Ariel
the terminal hand — quick to the bidding, quicker to report what she heard
When Quilltap runs inside a sandbox—Docker, the Lima VM, the WSL2 rootfs—your characters can do more than describe what they would do at a terminal. They can use one. A real PTY runs inside the sandbox, bound to the chat, accepting your keystrokes and producing output that the language model at the table can actually read.
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.
Terminal Mode
a real shell, in the Salon, bound to the chat
The Salon’s Terminal Mode opens a full
xterm.js + node-pty terminal in a split pane beside
the conversation, bound to the active chat. 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
When both Document Mode and Terminal Mode are active, the
right pane splits vertically—document on top, terminal
on bottom—with a draggable horizontal divider between
them. You can read what the LLM wrote in the document, run
something in the terminal that exercises it, and watch the
two move together without ever leaving the chat. The
rightPaneVerticalSplit ratio persists, so the
layout you set is the layout you get next time.
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.
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,
rightPaneVerticalSplit) so reloading the page
restores the pane, the session binding, and the layout
ratio. 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.
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 model has many other ways to run code in the sandbox.
The shell tools (exec_sync,
exec_async, and friends) execute commands
directly under Prospero’s arrangement and produce
their results as tool outputs the model can read. What the
interactive terminal offers that the shell tools do not is
continuity—a working directory and an
environment that persist between commands, an editor a
human can drive, a build that streams output live. Letting
the model type into that session would erase the boundary
that makes it useful as 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.
The Sandbox Boundary
why this is VM and Docker territory
Ariel exists only when Quilltap is running inside a sandbox—Docker, the Lima VM on macOS, the WSL2 rootfs on Windows. In Direct mode (the default, where the backend runs on Electron’s bundled Node and shares the host filesystem) there is no terminal feature, because there is no boundary between the AI and your machine that would make a live shell safe. This is deliberate, not a limitation: a character with terminal access on your bare host has too much of your house. Inside the sandbox she has only what you have given the sandbox, which is a far more answerable question.
Inside the sandbox, the terminal is the interactive companion
to the six shell tools Prospero arranges—chdir,
exec_sync, exec_async,
async_result, sudo_sync,
cp_host. Those tools cover non-interactive command
execution (run a script, install a package, copy a file out to
the host); Terminal Mode covers the interactive cases the tools
cannot (run an editor, watch a build stream, debug an
interactive program). The two surfaces share a sandbox and
share the workspace acknowledgement modal that gates first
use. sudo_sync still requires its own explicit
approval through the dedicated dialog; Terminal Mode does not
bypass that gate.
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 all available
from the terminal directly. The sandbox is a real working
environment, and Ariel is the spirit who attends it.
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 →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. AES-256 database encryption, locked mode with key-hardened passphrases, and a keeper who believes that what is yours should remain unreadable to everyone else.
Learn more →Pascal
The Croupier
Dice, coins, and persistent game state. Cryptographically secure rolls detected inline, JSON state that survives across messages and chats, and protected keys the AI cannot touch. 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 →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 →