Agent conversations
Read long agent conversations, navigate older messages, and reopen Artifacts.
An agent conversation opens on its most recent messages. Scroll upward to load older messages. The reader's position stays anchored while an older page arrives; a new reply follows the scroll only when the reader is already near the latest message. The transcript renders the rows in view, so a long conversation does not mount every message at once.
The inspector lists records, documents, and every published Artifact revision for the session, including entries from messages that have not been loaded into the transcript yet. Opening an Artifact reads its stored revision; loading or unloading transcript rows does not remove its content. If the inspector cannot load its resource list, it shows a retry action instead of an incomplete list.
Copying an assistant answer includes all of its messages, even when the beginning sits on an older page. The conversation loads the missing part on demand before copying.
Where a new conversation opens
New chat and its shortcut (⌘O, or Ctrl+O on Windows and Linux) open a new conversation in the side panel by default, so the page you were on stays in view. To have every new conversation open fullscreen instead, choose Settings › Preferences › New conversations › Fullscreen. The choice is saved on your profile in this workspace, so it follows you to every device; each workspace keeps its own.
Either way, a conversation already open keeps its own place: the expand and reduce controls in its header move that one conversation between the panel and fullscreen. On a page that is already a conversation, a new conversation always opens fullscreen in its place.
Turns and phases
A conversation advances in turns. A turn starts when the agent reads what is waiting for it — your message, a decided approval, the reports of the agents it waited for — and ends when the agent stops writing. Each turn ends with one outcome:
| Outcome | Meaning |
|---|---|
completed | The agent finished its answer, or reached a wait (see below). |
failed | The turn stopped on an error, such as a spent budget or a model that kept failing. |
cancelled | Someone stopped it. |
Between turns, a session is in one of three phases:
| Phase | Meaning |
|---|---|
idle | No turn runs and nothing is awaited. The next message starts a turn. |
running | A turn runs. |
parked | No turn runs, but the session still holds open approvals or awaits the agents it started. parkedOn says which: human or agents (human wins when both hold). |
A session has no end state. Its state carries the phase, parkedOn when parked, and the last turn's outcome; a failed or cancelled conversation is an idle one whose last turn failed or was cancelled, and it accepts the next message like any other.
Every wait ends the turn. A step that asks for approvals, or that starts agents, or sends them a follow-up, with wait: true, ends the turn as completed, and the session parks. Answering is not a continuation of that turn: once every approval of the batch is decided, or every awaited agent has reported, a new turn starts and reads the answers from the conversation. An approval left undecided for 24 hours expires; the agent sees the expiry in the turn that follows.
A message sent while the session is parked starts a turn at once. The open approvals stay open: the agent sees them as still pending in that turn, and a decision that arrives meanwhile waits for the turn to end, then starts its own. When a turn ends and something is still awaited, the session parks again.
A message sent while a turn runs waits; the agent reads it at its next step, or in the next turn. A turn acts for one person at a time: the waiting messages read together are those sent by the same person as the oldest one, and a message from someone else starts its own turn. If the server running a turn stops, the turn resumes by itself from its last completed step; a turn that cannot be resumed ends failed.
A message carries its sender's timezone. POST /agents/sessions/:id/messages takes an optional timezone, an IANA name such as Europe/Paris; the Standards app sends the browser's. A conversation's turn is dated in the zone of the latest message that carried one, and the agent states dates and times in it. A zone the server does not recognise is ignored. Without one, and always for runs, schedules and triggers, the agent works in UTC.
Acting on a session
Every action on a session is a request to the agent, answered with an outcome rather than with the session:
| Outcome | Meaning |
|---|---|
{ "status": "accepted", "seq": "…" } | The agent will act on it. created: false means an identical earlier request already answered it. |
{ "status": "withdrawn", "messageId": "…" } | The message you stopped was still waiting: it was taken back and never read. |
{ "status": "no_active_turn" } | The turn you named is no longer current. Nothing changed. |
DELETE /agents/sessions/:id stops a conversation. Its body names what you are stopping: { "turnId": "…" }, the running turn you observed on the stream (turn.started), or, for a Stop pressed before the turn started, { "clientMessageId": "…" }, the id of the message you sent. A message that is still waiting is withdrawn; one a turn already read stops that turn. A Stop on a parked session, naming its last turn, cancels the waits: the open approvals close, the awaited agents are stopped, and the session becomes idle with outcome cancelled. A request that names nothing, or a turn that has since ended, answers no_active_turn. PATCH /agents/runs/:id/cancel stops a run's current turn and answers the same outcome.
The other actions answer an outcome too:
PATCH /agents/sessions/:id/agent-readasks the running turn to read the waiting messages at its next step.POST /agents/sessions/:id/compactfolds the older history into a summary once no turn runs. The fold's result arrives on the stream ascompact.completed, not in the response.PATCH /agents/sessions/:id/messages/:messageIdreplaces a past message and everything after it, and answers{ outcome, clientMessageId }, the id under which the corrected message is written. A session with a running turn answers409withAGENT_INVALID_STATE: stop it first. A conversation run by a harness (Claude Code, Codex or Cursor, see Environments) refuses edits the same way.POST /agents/sessions/:sessionId/approvals/:approvalIddecides an approval and answers{ message, outcome? }. The agent resumes on its own once the whole batch is decided.POST /agents/sessions/:sessionId/requests/:requestId/answeranswers the agent's questions,requestIdbeing the id of itsask_questionscall, and answers{ message }. See Answering the agent's questions.
An interrupted turn needs no action from the client: the former resume endpoint is gone.
Forking a conversation
POST /agents/sessions/:id/fork copies a conversation into a new one and answers the new session. The copy is a conversation of its own: its own messages, turns, approvals and title, starting as "<title> (fork)". The original keeps running untouched, even when you fork it mid-turn. Send an optional client id (a UUID) to make a retry return the same fork instead of copying twice.
The fork holds every message the original had persisted when you forked it, in the same order. A message still waiting to be read is left out. A tool call that had no result yet (a step still running, an approval or a question still open) is closed in the copy with a fixed note saying its outcome stays with the original, so the fork never runs or decides it. The fork keeps the original's agent or configuration, model choices and approval mode.
Every session carries its lineage: forkedFromSessionId, the conversation it was copied from, and forkedAtMessageId, that conversation's last message in the copy. Both are absent on an ordinary session. forkedFromSessionId is cleared when the original is deleted; the fork itself is never touched. The Standards app nests a fork under its original in the conversation list. Only the person who started a conversation sees its Fork action, in the conversation's menu or as /fork in its composer.
| Status | Meaning |
|---|---|
403 | You did not start this conversation (only its creator can fork it), or it is a subagent or an autonomous run (SCHEMA_STATE_GATE). |
409 | The conversation is run by a harness (AGENT_INVALID_STATE), which refuses forks as it refuses edits. |
404 | The conversation does not exist, or you cannot see it. |
Send an aside with the fork, { "question": "…", "answer": "…" }, to append an aside exchange (see below) after the copy: the question as your message, the answer as the agent's, both marked as an aside so the agent reads them as one, with no turn started. A blank aside question answers 400.
Asking an aside
POST /agents/sessions/:id/aside asks a side question about a conversation without adding it to the conversation. The body is { "question": "…", "exchanges": [ … ] }, exchanges being the previous asides of your view as { question, answer } pairs, at most 20; the server keeps none. The answer streams back as text/plain. The agent answers from the conversation as it stands, including while a turn runs, and uses no tool. Nothing is written: no message, no turn, no memory, no notification. The model call is billed like any other, and an answer is kept short (about 4,000 tokens at most). Only the person who started the conversation can ask an aside about it. In the Standards app, type /aside followed by your question; the answer shows above the composer, and Continue as a conversation forks the conversation with the aside appended.
| Status | Meaning |
|---|---|
400 | The question is blank or too long, or more than 20 exchanges were sent. |
403 | You did not start this conversation (only the person who started it may ask an aside), you cannot start a turn in it, or it has no turn yet to ask about (SCHEMA_STATE_GATE), as a conversation on an environment never has. |
404 | The conversation does not exist, or you cannot see it. |
A refusal answers its JSON error before any answer text is sent. An answer that fails once it has started is cut off, so reading the response fails rather than ending as if the answer were complete.
Forked subagents
An agent that spawns a subagent chooses its context. "fresh", the default, starts the subagent from its mission alone. "fork" starts it from a copy of the conversation so far, with the agent's own instructions, tools and model, followed by the mission: use it when a fresh subagent would need too much background, or to try several approaches from the same point. A forked subagent reuses the conversation's prompt cache, so it costs far less than briefing a fresh one with the same context. It cannot ask the person questions, change the todo plan, load more tools or spawn subagents, and it runs only the tools the agent still holds, so it holds no permission the conversation lacks: a web search the model provider runs itself is left out when the agent no longer holds it. Only its final report reaches the conversation that spawned it. A fork cannot name another model.
Answering the agent's questions
When the agent asks questions, its ask_questions call stays open in the message until it is answered; asking never parks the session. Only the person who started the conversation answers it. Send the card's answer to POST /agents/sessions/:sessionId/requests/:requestId/answer:
{ "skipped": false, "answers": { "target": "publish_default", "scopes": ["read", "audit only"] } }Each answer is a string, or a list of strings for a multi-select question. A value the question did not offer is your free-text answer ("Other"), at most one per question, unless the question allows none. A question you walked past is left out of answers. The answer becomes the call's result, with each chosen option's label, the response carries the updated message, and the agent continues in a new turn as soon as none runs; a conversation waiting on an approval reads it when it resumes, one waiting on other agents when they report. A Stop after that leaves the answer for your next message.
| Status | Meaning |
|---|---|
403 | You did not start this conversation, or you can only read this agent (a reader cannot start its turns). |
400 | An answer names a question the call did not ask, has the wrong shape, or a value the question does not accept. |
404 | No open question has this id: it does not exist, the conversation was stopped, or you answered it by writing a message. Also returned when you can no longer see the conversation. |
Answering again answers the same message and changes nothing. Writing a message instead of using the card also answers the questions when you started the conversation: the agent is told you wrote instead and reads your message. Anyone else's message leaves the questions open.
Listing sessions
GET /agents/sessions lists the sessions you can see. It filters on phase (idle, running or parked) and on outcome, the last turn's (completed, failed or cancelled), alongside q, definitionId, mode, parentSessionId, rootSessionId, limit, offset and includeArchived. The query is strict: any other parameter, the former status filter included, answers 400 with SCHEMA_VALIDATION_FAILED instead of an unfiltered list.
The model a new conversation starts on
A conversation not bound to an agent starts on your personal default: Auto, or the model you last chose in the conversation's model menu. Choosing Auto there makes Auto your default; choosing a model makes that model your default. It is your chat.default preference: GET /users/me/preferences reads it, and PUT /users/me/preferences/chat.default sets it directly, with { "value": { "routing": "auto" } } for Auto or { "value": { "model": { "provider": "anthropic", "model": "claude-sonnet-5" } } } for a model. A model the catalogue has since retired reads as its closest live model. A conversation bound to an agent starts on that agent's configuration instead.
Attaching a voice memo
A voice memo — an .m4a, .mp3, .wav, .ogg, .webm or .flac, up to 50 MB — attaches to a message like any file. The agent reads its transcript, so every model takes it and none needs to hear audio. The transcript is made once and kept on the file: later turns reuse it, and the transcription counts toward your workspace's AI usage like any other model call. The memo itself also stays in the agent's files, under /workspace/attachments. A memo that cannot be transcribed, or one over 20 MB, reaches the agent only as that file.
Following an architect conversation
In architect mode, the agent's changes appear as a draft while the conversation runs. The page follows the object, view, or tab the agent is changing. An activity bubble shows the current change and stays visible while the agent prepares the next one. If you navigate manually, automatic following pauses so the page stays where you chose.
When the agent adds a sample record, its draft record opens in a read-only detail page. You can inspect its tabs before publishing the draft. The sample and schema changes become permanent only when you choose to publish.
Asking for access
In a conversation you are actually at — never a spawned child, a trigger run, or a schedule run — the AI can ask you for access it lacks instead of guessing. It shows a card with up to five items, each either:
- a catalog MCP server, with the reason it wants it. Connecting opens the consent in a new tab or a popup; the chat itself never navigates away. Before you click, the row states whether the connection will be private to you or shared with the workspace — shared when the AI asked for the whole workspace (
scope: "workspace") or the target agent is a workspace agent, private otherwise. Unless the AI asked for the workspace, the row stays inactive until that agent is known. When the connection is to be shared, only a connection shared with the workspace counts as connected: your private one never answers it. Connecting needs the same permission as adding a connector in settings; without it, the card is refused, whatever the AI asked. A connector you removed can be asked for again the same way. - an API key, saved as an environment variable. The AI tells you exactly what to create and with which rights, and links to the page to create it. You choose whether the key belongs to the conversation's agent or to the whole workspace; an agent-layer key overrides a workspace-layer key of the same name. The row states who ends up able to use it: only you, every member who uses this agent, or every member and their routines.
Each item targets the conversation's agent by default. It can instead name another agent you can edit, with agentId — for example the routine the AI is building for you in Architect mode — so the connection or the key lands where that agent can use it. A card never asks twice for the same key or the same connector.
The value you type goes straight to the environment-variable store. It never enters the chat, a tool result, or the model — the AI only ever learns what is now stored or connected, read back from real state, never what you typed. If a key already exists at the layer the AI would ask for, it is never asked again: the AI can see key names when it may read environment variables, never their values.
API history window
GET /agents/sessions/:id/messages?history=1 returns the newest 30 messages in chronological order with hasMore and an opaque nextCursor. Pass before=<nextCursor> to get the preceding page. An optional limit accepts 1–100. The cursor is exclusive and remains stable when another message is added. A request without history=1 retains the existing array response for clients that use the original endpoint.
The history page also returns committedSeq, the position in the session's event log that its messages already reflect. Each user message carries waiting: true while no turn has read it yet.
GET /agents/sessions lists conversations newest-created first. Pass orderBy=lastActivity to order them by the latest message either side wrote (creation when there is none), as the conversation history does; renames, read stamps and archives never move a conversation in that order. Page with limit and offset.
Following a conversation live
GET /agents/sessions/:id/stream?after=<committedSeq> streams the session's event log as server-sent events. Each event is one frame, id: <seq>, event: event, with the event as JSON data. The stream first replays every event after the cursor, then sends a control frame event: control with {"control":"up-to-date"} followed by {"control":"inbox"}, then follows the session live. The response opens with retry: 1000, the reconnect delay. When the agent writes its todo list during a turn, a todo-plan.updated event carries the plan as written, as data.plan.
Each event carries seq, type, data and emittedAt; the events of a turn also carry turnId, stepIndex and attempt:
type | Meaning |
|---|---|
chunk | A piece of the agent's answer: data is an AI SDK UIMessageChunk, unchanged. |
turn.started | A turn starts. clientMessageIds names the messages it read. |
turn.completed, turn.failed, turn.cancelled | A turn ends. data carries its outcome and the session's next phase. |
step.reset | The server running the turn stopped. Drop what the abandoned attempt (data.abandonedAttempt) left from stepIndex on; attempt is the one that starts, at that same stepIndex: a turn's step numbers run on across its attempts. |
message.consumed | The running turn read more waiting messages, named in clientMessageIds. |
input.requested, input.resolved | An approval or a design review opened, or a request closed (data.requestId). A question emits no input.requested: it never parks the session, and shows as the open tool call in its message. |
session.state | The session's state changed (data.state). |
todo-plan.updated | The agent wrote its todo list (data.plan). |
compact.completed | A requested fold ran: data.result is folded, nothing or failed, with data.error a code when it failed and data.commandSeqs the requests it answered. |
The log is a buffer, not the record: an event is purged about an hour after what it built is stored (never while its turn runs). The history page is the durable read.
{"control":"inbox"}means the session's inbox may have changed: a message was sent, withdrawn, or an approval was decided. Refetch the history page. It comes once after every catch-up, and within two seconds of any change; a repeat is harmless.{"control":"lease-ended"}ends the response after 60 seconds. Reconnect at the lastidyou received, asafteror asLast-Event-ID(which takes precedence).410withAGENT_SESSION_STREAM_CURSOR_EXPIREDmeans the events after your cursor are no longer retained. Reload the history page and reconnect at itscommittedSeq, which is always accepted.
Read the stream with a fetch-based SSE client: EventSource cannot read a refusal's status or code, so it cannot tell a 410 from a lost connection. The cursor is required: a request with neither Last-Event-ID nor after, or with a cursor that is not a non-negative integer, answers 400. An invisible session answers the same 404 as its messages, whatever the cursor.
GET /agents/sessions/:id/conversation-overview returns the inspector's resource entries, current human requests, and a short first-message preview without the full transcript. Both reads use the session's normal access rules.
Slack threads
A conversation can be linked to a Slack thread. A Slack trigger establishes the thread for its run; sending a top-level Slack message from an app conversation can link a thread back to that conversation. Human replies enter the linked conversation as external message content. They run under the authority pinned when the thread was linked, rather than taking on the Slack sender's permissions.
The agent's final answer is mirrored to linked threads, including when you continue from Standards. A turn that explicitly sent its answer to that thread is not mirrored twice. The first mirror can include Open in Standards, and the app remains the place to inspect the full conversation, tools and approvals. Slack activity updates are best-effort; a missing activity indicator does not mean the run stopped.
Stopping or archiving a conversation affects whether later trigger events can continue it. Provider disconnection prevents further Slack delivery, without deleting conversation history. For the rules that start a new conversation after a trigger fires again, see GitHub and Slack triggers.
Next steps
Read workspaces and API keys to connect an application to a workspace.
Agents and routines
Run agents on their own with routines, start them from record, form, schedule and webhook triggers, and switch a routine on or off.
Environments
Pick the harness that runs a conversation, a routine or an agent, and the machine it runs on — prepared once by a setup script, used in a copy every time.