Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ AgentStudio provides a streaming chat interface where the assistant can call too

Editing a message or regenerating a reply cuts the conversation back so the model sees only what was kept plus the message it is answering. In a project (or an agent with a persistent workspace) the agent's file changes are checkpointed every turn, and Edit and Regenerate offer to restore the files the dropped replies changed — showing the files and line counts first, and asking again before overwriting uncommitted changes in an imported repository. **Compact Conversation** runs the SDK's own `/compact`. See [docs/chat/chat.md](docs/chat/chat.md).

When the agent needs a decision it asks with the Agent SDK's own AskUserQuestion, shown as a question card: a header chip, option cards with a recommended badge, a sandboxed HTML preview of each option where one helps, multi-select, and a free-text "Other". It can be answered in the chat, from the composer, or from the /review inbox, and never answers itself. See [docs/chat/spec.md](docs/chat/spec.md#questions-from-the-agent).

Chats run on Claude models through the Claude subscription, at no per-token cost. The model pickers — the composer (its model pill and the `/model` command), the default model and each agent's model — offer only models that can actually run here: the current Claude models Claude Code knows, plus, when the operator configures an Anthropic-compatible gateway such as OpenRouter's, the gateway's models, labelled **Gateway · paid** and priced per token in the usage ledger. The gateway is off by default. See [docs/llm/llm.md](docs/llm/llm.md).

The message box has two shortcuts. Typing `@` lists the files in the chat's workspace (a bound project's folder, or an agent's persistent workspace) and inserts the chosen file's relative path. Typing `/` at the start opens a command palette over the app's own actions: `/compact`, `/model`, `/agent`, `/research`, `/plan`, `/effort`, `/attach` and `/voice`. Both work from the keyboard and by tapping on a phone. See "Composer shortcuts" in [docs/chat/spec.md](docs/chat/spec.md).
Expand All @@ -18,7 +20,7 @@ Replies can be read aloud: a speaker button on each reply plays it through an Op

Beside each chat sits a right rail with two tabs: **Preview** (a workspace file or a web page) and **Files** (every file the agent changed in the chat, with +/- counts; click one to preview it). It stays folded to a thin strip until something opens a preview or you expand it, and remembers whether you left it open; on a phone it is a drawer. The context ring and metered cost sit in the chat's header, and each reply links to its run's full tool timeline. See [docs/chat-console/chat-console.md](docs/chat-console/chat-console.md).

Creation workflows are chat-led: New Agent and New Skill actions launch a fresh conversation with a seeded creation prompt. The assistant gathers missing requirements (optionally with ask_user), then executes directly with tool-level approvals where configured.
Creation workflows are chat-led: New Agent and New Skill actions launch a fresh conversation with a seeded creation prompt. The assistant gathers missing requirements (optionally with a question card), then executes directly with tool-level approvals where configured.

### Agents

Expand Down
38 changes: 34 additions & 4 deletions docs/chat/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,37 @@ A run's tool calls and events are on its own page, `/runs/<id>`, linked from eac

### Inline approvals and answers

Approval requests and `ask_user` questions render inline in the thread, but are also reflected in the global Review Inbox. Resolving from either place updates the same durable state.
Approval requests and the agent's questions render inline in the thread, but are also reflected in the global Review Inbox. Resolving from either place updates the same durable state.

### Questions from the agent

When the agent needs a decision from you before it carries on, it asks with **AskUserQuestion** — the Agent SDK's own question tool, which replaced AgentStudio's home-made `ask_user` in September 2026 (#4). A call holds one to four questions.

**What the card shows.** Each question gets:

| Part | What it is |
| --- | --- |
| Header chip | A short label for the question ("Layout", "Auth method") |
| Question | The full question, in a sentence |
| Option cards | Two to four choices, each with a label and a line saying what it means. The one the agent recommends carries a **Recommended** badge |
| Preview | For options that are easier to compare by eye (a layout, a snippet, a configuration), a small rendered picture of what the choice produces. It shows the option under your pointer or keyboard, otherwise the one you chose, otherwise the recommended one. Beside the options on a wide screen, below them on a phone |
| Other | Always there: type your own answer instead of — or, on a "choose any" question, as well as — the options |

Some questions say **Choose any that apply**: tick as many options as you like. The rest take one answer, and typing in Other (or clicking Other itself) replaces the option you picked. Just moving through Other with the keyboard changes nothing, so you can pick an option and Tab on to Submit. With several questions the card steps through them (Question 1/3, Next, Submit), and Submit waits until every question has an answer.

**How you answer.** In the card, in the chat composer (typing a reply while a question is waiting answers it), or from the /review inbox, which shows the same card. Whichever comes first counts. Once the server has recorded it, the card shows each question with your answer under it, and so does the conversation after a reload.

**Several questions at once.** The agent can ask more than one AskUserQuestion in the same step, so two or three cards can be waiting side by side. Each card sends its answer for its own question only. A reply typed in the composer (or given in the modal) goes to the newest card still waiting; once that one is answered, the next one still waiting takes its place.

**If nobody answers.** The question waits five minutes, like a tool approval. After a minute you get a "Needs input" notification. After five, the agent is told nobody answered and that it must not assume an answer — it carries on only with what does not depend on it, or ends its turn and says what it needs. Stopping the run settles the question at once. A question never answers itself: the SDK's own idle auto-continue (`askUserQuestionTimeout`) is set to `never`, because on a box that runs while you sleep an auto-picked option is a decision you never saw.

**Who can ask.** Only the agent in a chat. A delegated subagent cannot — it is told to put the question in its result so the agent that delegated to it can ask. Automations, monitors and CI-fix runs have no one to ask and are never offered the tool; if the model tries anyway the call is refused, and the run does not wait.

**Previews are safe to show.** A preview is HTML the model wrote, and the model may be repeating text from a web page or a repository. So it is never put into the page. It is shown in a sealed frame that cannot run scripts, cannot reach the app or your session, cannot submit forms or open windows, and cannot load anything from the internet — an image pointing at someone's server is simply not fetched. Tags that could act on the frame itself are shown as text. A preview over 12,000 characters is dropped.

**What was checked in the SDK** (`@anthropic-ai/claude-agent-sdk` 0.3.278, bundled CLI 2.1.278), rather than assumed: `toolConfig.askUserQuestion.previewFormat` exists and is set to `html` (the default is markdown, for a terminal); `askUserQuestionTimeout` is a Settings field, reached through the `settings` option; the tool always asks permission, so each call reaches the app's permission callback, which answers it by returning the call's input with the answers added (question text to answer, several choices comma-separated); and the tool is only switched on when a permission callback is installed, which the chat engine always does. The CLI also reads back and reports the answers it was given, which is what the saved transcript shows.

Transcripts from before the change keep their `ask_user` blocks, and those still show each question with its answer.

### Diff and artifact preview

Expand Down Expand Up @@ -299,7 +329,7 @@ How the controls that exist today behave:
- **Coming back to a running turn.** Opening a conversation whose turn is still running — after a reload, or from another tab — shows that turn streaming again, with its tool and approval cards and the Stop button. Text written before you came back appears once the turn finishes.
- **One turn at a time.** A message sent while a turn is still running is not sent. The page says so, keeps the message for Retry, and shows the running turn instead.
- **Allow / Deny.** An approval card only shows a call as approved or denied once the server has recorded the answer. If it could not be recorded (the approval timed out, or was answered in another tab), the card keeps its buttons and says why.
- **Answering a question.** When the agent asks a question (`ask_user`), the answer only counts once the server has recorded it. If the question is no longer waiting (it timed out, was answered in another tab, or its turn ended), the page says so and shows the conversation as the server has it, rather than closing the question as if the answer had gone through. An answered question shows the answer under it straight away, and again after a reload. It no longer keeps a live Submit button that does nothing.
- **Answering a question.** When the agent asks a question (see [Questions from the agent](#questions-from-the-agent)), the answer only counts once the server has recorded it. If the question is no longer waiting (it timed out, was answered in another tab, or its turn ended), the page says so and shows the conversation as the server has it, rather than closing the question as if the answer had gone through. An answered question shows the answer under it straight away, and again after a reload. It no longer keeps a live Submit button that does nothing.
- **Switching conversations mid-turn.** Opening another conversation while a reply is streaming shows only the other conversation. Nothing from the first one comes along: not its reply, its tool cards, its Stop button, its error message or its Retry. Leaving is not a Stop. The first turn keeps running, saves its own reply, and shows again with Stop when you go back to it.
- **A turn that ends in an error.** Some turns end in an error after the reply was already saved, for example when the agent reaches its maximum number of steps or the model provider is overloaded. The page shows the error and keeps the one saved reply. It used to save a second, partial copy of the same reply.
- **Background tasks.** A command the agent starts in the background (a dev server, a watcher) shows as a chip in the header while the turn runs, with a button to stop it. The chips go away when the turn's stream ends, because ending a turn also ends the commands it started. If a stop does not work, a short message under the header says why — for example that the turn had already ended.
Expand Down Expand Up @@ -334,7 +364,7 @@ Every assistant reply has a speaker button beside Copy. It reads the reply throu

### How replies are displayed safely

Assistant replies, thinking, subagent results and `ask_user` questions are written by the model, and the model may be repeating text it picked up from a web page, a repository or a tool result. Someone who plants instructions there can try to make the model write HTML that would run inside the app, or an image link that quietly sends data to their server the moment the reply is shown. So replies are displayed as formatted markdown, but under these rules:
Assistant replies, thinking, subagent results and the agent's questions are written by the model, and the model may be repeating text it picked up from a web page, a repository or a tool result. Someone who plants instructions there can try to make the model write HTML that would run inside the app, or an image link that quietly sends data to their server the moment the reply is shown. So replies are displayed as formatted markdown, but under these rules:

| Content in a reply | What the reader sees |
| ------------------ | -------------------- |
Expand Down Expand Up @@ -391,7 +421,7 @@ This domain follows [../ui/spec.md](../ui/spec.md) and defines the primary app-s

- Surfaces: session list (with agent filter, project grouping, and expandable run tree), chat thread canvas, composer, mode selector, live run HUD, inline action cards, and the right rail (Preview + Files).
- States and badges: running, blocked, needs-input, queued interjection, completed, failed, and pending approvals count.
- Blocking actions: plan approvals, tool approvals, and ask_user responses must resolve through durable review items.
- Blocking actions: plan approvals, tool approvals, and answers to the agent's questions must resolve through durable review items.
- Mobile behavior: the right rail opens as a drawer from the chat header; blocking cards remain visible near composer; session tree uses progressive disclosure to avoid deep nested panes.

## References
Expand Down
2 changes: 1 addition & 1 deletion docs/hooks/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ Not every event below is raised yet. Which ones fire today, on the chat path and
| `on_evaluator` | `runId`, `verdict`, `findings` | On evaluator result |
| `on_subagent_spawn` | `parentRunId`, `childRunId`, `agentId` | On `run_subagent` call |
| `on_approval_required` | `runId`, `toolName`, `args`, `token` | On pending approval |
| `on_user_question` | `runId`, `questions`, `token` | On `ask_user` call |
| `on_user_question` | `runId`, `questions`, `token` | When the agent asks a question (AskUserQuestion) |
| `on_run_failed` | `runId`, `error` | On run error |
| `on_skill_loaded` | `runId`, `skillSlug`, `loadKind` | When a skill summary or body is loaded |
| `on_tool_output_archived` | `runId`, `toolName`, `handle`, `wasSummarized` | When large tool output is offloaded |
Expand Down
4 changes: 2 additions & 2 deletions docs/notifications/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ Notification preferences are stored in `appSettings.notificationPrefs` and contr
| Key | Switch in Settings | What it sends today |
| --------------- | ------------------ | ------------------- |
| `taskCompleted` | Task completed | A research run finished its report |
| `needsInput` | Needs input | A chat run has been waiting for a tool approval or an answer to an `ask_user` question for a minute. Not straight away: someone watching the chat answers in seconds. The run gives up after five minutes, so this leaves about four to answer. The notification links to the chat |
| `needsInput` | Needs input | A chat run has been waiting for a tool approval or an answer to one of the agent's questions for a minute. Not straight away: someone watching the chat answers in seconds. The run gives up after five minutes, so this leaves about four to answer. The notification links to the chat |
| `agentErrors` | Agent errors | An automation run failed for good, or a check failed on a pull request an agent opened |

Two kinds of notification are sent whatever these switches say, because the user set each one up on purpose and turns it off where they set it up:
Expand Down Expand Up @@ -87,7 +87,7 @@ All notifications are persisted to the `notifications` table regardless of wheth
- Mark individual or all as read
- Notifications older than 90 days are soft-hidden (not deleted)

The inbox is the fallback for users without push subscriptions. For approvals and `ask_user` blocks, the chat session UI is the primary surface — the notification is a secondary signal for users who are not actively watching the chat.
The inbox is the fallback for users without push subscriptions. For approvals and the agent's questions, the chat session UI is the primary surface — the notification is a secondary signal for users who are not actively watching the chat.

## Web Push Setup

Expand Down
2 changes: 1 addition & 1 deletion docs/observability/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ Time-series metrics storage. Rolled up from run events, job logs, and hook invoc
| Item type | Severity | Triggered by |
| ------------------------- | -------- | ----------------------------------------------------------- |
| `approval_request` | critical | Tool call requires human approval |
| `user_question` | critical | `ask_user` tool call waiting for answer |
| `user_question` | critical | A question from the agent (AskUserQuestion) waiting for an answer — answerable from the inbox with the same card as the chat |
| `evaluation_failure` | warning | Evaluator returned `fail` verdict |
| `job_failure` | warning | Job exhausted all retry attempts |
| `job_stuck` | warning | Job whose worker kept dying mid-run, out of attempts |
Expand Down
Loading
Loading