ArchitectureDraft
ADR 0024 · Chat: new sessions, the box's folders, a terminal in the browser, and questions
Status: proposed, 27 Sep 2026 · Workstream: chat · Spec: section 7.8 (the switchboard), Chat
The problem
On the Deck, the user could read and answer sessions but could not start one, could not see the box's folders, and had no terminal. A session read like a chat log, not like Claude Code: tool calls were one-line chips, and every reply was labelled "claude". Claude Code's own questions (AskUserQuestion) never reached the Deck as questions. They came as a permission ask for a tool called AskUserQuestion, which the user could only allow or deny. A permission ask showed a 200-character summary, not what would run.
Decision
Events stay small; the transcript is the rich record. Tool inputs and outputs, thinking and
diffs are never written to the event log (spec 6). The session view reads Claude Code's
transcript through recall.transcript, one structured block per line: user, text, thinking,
tool (input and output, redacted and capped), and a turn footer with time and tokens.
transcripts/ is still the only code that reads the format. Live thread.* events draw a turn
as it streams; when the turn ends, the view reads the transcript from where it left off and
replaces the live rows in place. The same read works for terminal sessions the switchboard never
ran, so the Deck mirrors the terminal whoever started the session. recall.transcript answers a
person's surfaces only: tool output holds whatever the session read.
Questions are asks with a kind. A can_use_tool request for AskUserQuestion becomes an ask
of kind question, carrying its questions (options, multi-select, previews). The answer goes
back as the Agent SDK does it: allow, with updatedInput.answers keyed by question. Every other
ask is kind permission and carries detail (the whole command, the edit's old and new text,
the URL), redacted and capped, in the asks table only, never in the event. When Claude Code
offers permission suggestions, the ask offers "Always for this", which allows with
updatedPermissions, or "Always in <project>", which writes the rule to that project's
.claude/settings.local.json. Answers need no presence proof (see "No nagging" below).
A terminal is a pty, not a session. core/term runs the user's login shell under script
(util-linux on the box, BSD on a Mac), so there's no native dependency. Output goes over a
ticketed WebSocket (/v1/streams/term/pty, as Glass does) and is never written to the event log
or vyred's log. Opening one needs no passkey (see "No nagging"). A terminal belongs to the screen
that opened it (caller, tailnet node and surface), and only that screen can reattach. A viewer leaving never ends a terminal: with nobody attached the box keeps it for term.keep_hours (12 hours by default), then ends it; term.close or the shell exiting ends it at once (ADR 0029, R4). Guests,
agents and models can't reach any term tool. The folder must pass the files guard.
No nagging. The user decided that Vyre asks for Touch ID or a passkey only to pair a device,
to release a vault secret, and to send, post or pay outside. Answering a session's ask (including
"always" in a project) and opening a terminal are the owner's own actions on their own screens,
so neither asks for proof. threads.answer left the floor's human-only list, and term.unlock is
gone. What still keeps others out: the tools' caller allowlists (a person's surfaces only; guests,
agents and models are refused), and a new floor list, PERSON_ONLY in core/presence
(threads.answer, term.open, term.attach), which the harness rules use to refuse a model's
shell that names one of these tools, as they do the human-only list. The cost: a model that gets
past the harness rules and reaches vyred as a person's surface could answer its own asks. Before
this, a presence proof stopped that. The user runs on bypass permissions, so this matters less
in practice than the prompts did.
Folders come from the files guard. files.dirs and files.recent list the box's folders
through core/files/safety.js, so the folder browser can never show the vault, Vyre's home, a
dot folder or anything outside the roots.
Names. In Chat, the model's replies read "Vyre", or the agent's name in an agent's thread. The user's own messages read "you".
Consequences
- A live session that Recall has not indexed yet is read straight from its transcript file.
- The Deck's rich view depends on Claude Code's transcript format. It degrades to the old
event-only view when
recall.transcriptis missing or fails. - The tailnet listener must pass WebSocket upgrades through for the terminal to work from a Deck at the box's address. Until it does, the terminal works only through vyred's socket (see team/archive/work-journals/chat.md, Needs from others).