---
title: "ADR 0024: Chat, new sessions, the box's folders, a terminal and questions"
summary: Chat starts sessions in a project or a folder on the box, opens a terminal in the browser, and shows Claude's questions and permission asks as cards the owner answers with no passkey.
audience: builders, agents
owner: docs
status: draft
---

# 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).
