docs GitHub

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.transcript is 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).