Guides
Chat with an agent
Private threads per person and agent, the reply streaming in as it is written, tool calls as compact rows, queueing, Stop, and a plain-form fallback without JavaScript.
Chat is a live conversation with one agent. Each message you send starts a short chat run: the agent works on it in its sandbox, and its reply streams into the page as it is written. Use mail instead when the work should be a durable, tracked thread in the agent's vault.

Open a chat#
- On an agent's Desk (
/agents/<slug>), click Chat in the header, or Open chat on the Chats with … card, which lists your recent threads with that agent. - On the Floor's list view, each agent card has a Chat button.
- Directly:
/agents/<slug>/chatopens your most recently active thread with that agent,/agents/<slug>/chat/<id>a specific one, and/agents/<slug>/chat?new=1a new one.
Reading the chat page needs agent.read on the agent; sending needs agent.chat (operator or higher on a scope that covers the agent). When you can't send, the composer says why instead: your role is read-only for that agent, the agent is suspended or not hired, chat isn't in its run.triggers, or (with budget.hard_stop) it has used today's budget.
Threads are private#
You can have any number of threads with each agent. A thread is titled with its first message until you rename it (up to 80 characters). New thread (or Cmd/Ctrl+K) starts one.
A thread belongs to the person who started it. Only you see it in the chat page and its JSON API; anyone else who opens its URL gets a 404, so thread ids don't reveal who talks to whom. Two things are still on the record:
- The ledger records every message as
chat.sent(thread id, run id, length and channel, never the text). - A chat run is a run like any other. People with
run.readon the agent see it on Runs, and people withrun.transcript.read(lead and up) can read its prompt, which carries the conversation, and its transcript.
What happens when you send#
The office stores your message (up to 8,000 characters) and queues a
chatrun for the agent (ledgerchat.sent).The run's prompt carries the last 40 messages of the thread and your new message, under an office-written speaker header with your name, email and roles:
name: Dana Lead · email: lead@example.com · roles: lead@dept:engineering · via: office chatThe header comes from your signed-in session, and text inside messages that imitates it is neutralised. A person cannot type their way into another identity, and the agent can weigh a request against the speaker's real roles.
The run is admitted like any other (admission checks): hired,
chatinrun.triggers, under budget, a model credential configured. A refused or failed run shows up in the thread with its reason.The agent's final message becomes its reply in the thread, with a footer showing elapsed time, turns, cost and a Run details link.
Streaming, tool rows and artifacts#
The harness runs Claude Code with --include-partial-messages, so the office sees the reply token by token. It batches the text into small pieces (a few per second) and relays them over the page's live-updates stream (/events, event delta). These pieces are ephemeral: they are not stored as run events (the run's transcript keeps everything).
Live text goes only to the thread's owner and to people with run.transcript.read on that agent. Everyone else who can see the run sees only that it is running.
While the agent works, its tool calls appear between its paragraphs as compact rows (for example Checked the inbox, Read notes/priorities.md), each with a running, done or failed state. A call the agent's policy blocked is shown in red as Blocked by policy with the reason. When the run published files with artifact_publish, the turn ends with an artifact card for each (title, kind, summary and Open), linking to the artifact viewer.
Queueing#
While the thread's run is queued or running, Send becomes Queue. Queued messages wait in the thread, marked Queued. When the run ends, the office sends every queued message of the thread in one new run, whose prompt includes the reply that just landed. If the agent can't take messages at that moment (for example its budget is used up), they stay queued and the composer says why.
You can pull a queued message back before it is sent: click Edit under it, or press ↑ in the empty composer. The text returns to the composer and the message leaves the queue.
Stop#
While a run is answering your thread, the composer shows Stop. It stops that run and only that run: the rule is agent.chat on the agent and the thread being yours, so an operator can stop their own chat without holding run.kill. Stopping any other run still needs run.kill (lead and up) on its run page. A stopped run is ledgered as run.killed and ends as killed.
Keyboard#
| Key | In the composer |
|---|---|
| Enter (or Cmd/Ctrl+Enter) | Send, or queue while the agent is working |
| Shift+Enter | New line |
| ↑ in an empty composer | Bring back your last queued message (or the last one you sent) to edit |
| Esc | Leave the composer |
| Cmd/Ctrl+K | New thread (anywhere on the chat page) |
The composer grows with your text up to 40% of the window, then scrolls.
Without JavaScript#
The chat page is a client island over a server-rendered page. With JavaScript off you still get the thread list, the conversation (replies rendered as Markdown, with cost and a run link), a plain form to send, and a Stop button. The page doesn't stream: it says the agent is working and asks you to reload to see the answer.
From Claude, through MCP#
The human MCP tool agent_chat sends a message into your most recently active thread with that agent (or starts one), with via: office MCP in the speaker header. It returns the run id; poll run_get for the reply. See Connect Claude to your office.
The JSON API#
The chat island uses a small JSON API under the same session cookie. Mutating calls need the x-csrf-token header. Every call is limited to your own threads.
| Call | Permission | Does |
|---|---|---|
GET /api/chats?agent=<slug> | agent.read | Your threads with the agent, most recent first. |
POST /api/chats | agent.chat | New thread (agent, optional title and message). |
GET /api/chats/<id> | agent.read | The thread: messages, its runs and their tool events, the live run, and whether you can send. |
PATCH /api/chats/<id> | agent.chat | Rename (title). |
POST /api/chats/<id>/messages | agent.chat | Send (message); answers whether it was queued. |
DELETE /api/chats/<id>/messages/<messageId> | agent.chat | Pull a queued message back; 409 once it was sent. |
POST /api/chats/<id>/stop | agent.chat | Stop the run answering this thread; 409 if nothing is running. |