The composer is the box at the bottom of every chat. It starts three lines tall and grows with what you type, up to nine lines, then scrolls. Under the box is a bar with the attach, editor and dictation buttons on the left, the model, effort and mode pills, any switches the agent publishes, the Follow live edits toggle, and the send button on the right.
What you type is kept per chat tab. Switch to another tab, or close the chat and reopen it, and the unsent draft is still there.
Send a message
Type and press Enter. Shift+Enter starts a new line. The send button
does the same as Enter; while a turn is running it turns into a stop
button.
A message can be text, attachments, or both. A message with only an attachment is fine: “look at this” can be the image alone.
Code blocks
When the caret is inside an open code fence (you typed three backticks and
have not closed them), Enter adds a line instead of sending half a block.
A hint under the box says Enter adds a line inside the code block.
Cmd+Enter sends.
Cmd+Enter always sends: inside a fence, with a completion menu open, from
anywhere in the box.
Long drafts
Once a draft passes about 2,000 characters, the bar shows a rough size, for example about 1.2k tokens. It is an estimate, at four characters a token, so you can tell before sending that a paste is bigger than you thought.
Keys in the composer
| Keys | What it does |
|---|---|
Enter |
Send. While a turn runs, steer it or queue (see Steering and the queue) |
Shift+Enter |
New line |
Cmd+Enter |
Send, even inside a code fence or with a menu open |
Option+Enter |
While a turn runs, queue for the next turn |
Cmd+Shift+Enter |
Steer with the oldest queued message, or send if nothing is queued |
ArrowUp, ArrowDown |
At the start of the box, walk back and forward through what you sent |
Option+ArrowUp |
At the start of the box, edit the newest queued message |
Ctrl+C |
Empty the box, the way a shell prompt does |
Cmd+S |
Stash the draft and empty the box. In an empty box, bring a stashed draft back (see Stash a draft for later) |
Escape |
Close an open menu. Otherwise drop a recording, cancel a queue edit, or stop the running turn |
Tab |
With a menu open, accept the highlighted row |
Recall what you sent
Press ArrowUp with the caret at the very start of the box to bring back the
last message you sent in this chat. Keep pressing to go further back, up to
your last 50 messages. ArrowDown walks forward again, and past the newest
one gives you an empty box. Once you start walking, the caret can be anywhere;
ArrowUp keeps going back.
A recalled message is a fresh draft. Change it and send it as a new message; the original stays where it was in the transcript.
Clear the box
Ctrl+C empties the text in one keystroke, so a prompt abandoned halfway
through does not have to be selected and deleted. Attachments stay, each with
its own remove button. With an empty box, Ctrl+C does nothing.
Stash a draft for later
Halfway through a prompt, something more urgent comes up. Press Cmd+S and
the draft, with its attachments, goes into the stash and the box empties.
Write and send the urgent thing, then press Cmd+S in the empty box to get
the draft back.
- With one stashed draft,
Cmd+Sin an empty box brings it straight back. - With several, it opens a menu above the box, newest first. Each row shows
the first line of the draft, how many attachments it has, and how long ago
it was stashed. Move with
ArrowUpandArrowDownand pressEnter, or click a row, to bring it back.Backspace, or the x on a row, discards that draft.Escapecloses the menu. - Bringing a draft back takes it out of the stash.
The stash is one list for all of Tori, not per chat or per project, so a
draft stashed in one chat can come back in another. Its attachment labels are
numbered again for the chat it lands in: [Image 1] may come back as
[Image 2], and the text changes to match. An attachment the new chat’s
agent cannot open stays in the stash on its own, its label is taken out of
the text, and a message says what that agent does open.
The stash holds 20 drafts. Stashing a 21st drops the oldest. It is kept on disk, so it survives a reload and a relaunch.
If you start typing before a draft has come back, your typing wins and the draft stays in the stash.
Cmd+S does nothing in the composer while you are editing a queued message,
while the draft is open in the editor, or while a new chat’s first message is
waiting for its session to start.
Discarding a draft, or one dropping off the end, deletes the files you pasted or dropped into it, unless a chat or another stashed draft still uses them.
Mention a file with @
Type @ and start typing a file name. A menu above the box lists matching
files in the project, best match first. Move with ArrowUp and ArrowDown
and press Enter or Tab to pick one, or click it. Escape closes the menu
without picking.
The file becomes an attachment chip above the box, and its label, like
[file 1], takes the place of what you typed, so your sentence still reads
“look at [file 1]”. The agent is handed the file’s path and reads it off
disk. Nothing is uploaded.
The first @ in a chat lists the project’s files once; later mentions use
that list.
You can also mention a file by dragging it into the composer from the Files
tree or from an editor tab. A dragged file is a mention, the same as @.
Reference a session with @
The @ menu also lists the repository’s other sessions, above the files: up
to five that match what you typed, matched on the session’s name or title.
They come from the whole repository, every worktree included, not just the
folder this chat runs in. The chat you are typing in is never listed.
To see only one kind, start with a scope:
@session/lists sessions only, all of them that match.@file/lists files only. Use it for a folder that is literally namedfileorsession, as@file/session/....
Picking a session puts [Session: title] in your sentence and a chip above
the box. Brackets in the title are dropped, and a second session with the
same title in one message becomes [Session: title (2)]. The agent gets the
session’s id, title, agent and project, and reads the conversation itself
with the session_history tool, so nothing from that session is copied into
your message. In the transcript the token is a chip: hover it for the title
and agent, click it to open or focus that session.
Session references need the tori tool server, which Claude, Codex and
opencode chats get. Pi does not, so a Pi chat’s @ menu lists files only.
Pi sessions can still be referenced from the other agents.
Reference a project or a space with @
@projects/ and @spaces/ walk your projects the way the sidebar lays
them out, one level per /:
@spaces/lists your spaces, and@spaces/work/lists the projects in one of them.@projects/lists the projects in the space this chat belongs to.- Under a project you get its checkouts (worktrees, or the checked out branch of a plain repository) and its sessions from every checkout.
- Under a checkout you get its files.
Each level is named by the last part of its folder, so a space called
“Client Work” in a folder named client-work is @spaces/client-work/. Two
projects whose folders share a name are told apart as tori and tori-2, in
the order the sidebar shows them. The menu shows that name next to each row.
Type part of it to filter. With a row highlighted, / opens it and fills in
its name. On a session / does nothing, since there is nothing under it.
Under a checkout / is just part of the file path you are typing.
Enter, Tab or a click picks the highlighted row:
- A space puts
[Space: name]in your sentence. The agent gets the space’s name and each of its projects’ names and folders. - A project puts
[Project: name]in your sentence. The agent gets the project’s name, its folder and its space. For more, it can use theprojects_listandsessions_listtools. - A session is referenced the same way as above.
- A checkout opens, the same as
/. - A file is attached by its full path, as
[File 1].
Brackets in a name are dropped, and two different projects or spaces with the
same name in one message become name (2). In the transcript, click a project
chip to show that project in the sidebar, or a space chip to switch to that
space. Neither click changes which branch you have selected.
Like sessions, projects and spaces need the tori tool server, so Pi chats
do not offer them.
Reference a pull request with
Type # at the start of a word to list the project’s open pull requests,
newest first, each with its title. Type a number to filter by number, or a
word to match titles. Enter, Tab or a click picks the highlighted one, and
[PR 123] takes the place of what you typed. A # inside a fenced code block
never opens the menu.
When you type a number the list does not hold, such as an old or merged pull
request, the first row offers to look it up. Tab fetches it and makes the
reference. Enter sends your message exactly as typed, so “fixes #42” still
works as plain text. If the number is not a pull request in this repository,
Tori says so and leaves your text alone.
The reference shows as a chip above the box. It carries the pull request’s
number, title, link, state, draft flag and branches as they were when you
picked it, and tells the agent to read the rest with the pr_get tool. In the
transcript, [PR 123] is drawn as a chip: hover it for the title and state,
click it to open the pull request in a tab. Removing the chip removes the
token from your sentence.
A reference rides along in the queue, the stash and a relaunch the same way an attachment does.
Run a slash command with /
Type / at the start of a word to list the agent’s own slash commands, the
same ones it offers in its terminal interface: /compact, /usage, your own
custom commands and so on. The list comes from the agent itself, so it
matches whatever agent and version the chat is on, and a new chat shows the
list the agent gave last time.
Each row shows the command, the argument it expects if any, and a
description. Picking one puts /name in the box with the caret after it.
The argument hint is shown, never inserted.
What a command prints, like the output of /usage, /context or /cost, is
shown in the transcript and is still there when you reopen the chat.
Attach files and images
There are three ways to attach a file:
- Click the + on the bar and pick one or more files.
- Paste. A copied image or file attaches itself.
- Drop files from Finder onto the composer. While you drag, the composer says what this agent can take, for example Drop to attach: image, pdf, file.
Every attachment is written under ~/.config/tori/attachments/ and the agent
is handed its path, the same way a mention works. Nothing is embedded in the
conversation as bytes.
Each attachment gets a label by kind, numbered per chat and never reused:
[image 1], [pdf 1], [file 2]. The label goes into your message where
the caret was, or where you dropped the file, so you can write “compare
[image 1] with [image 2]” and the agent knows which is which.
What each agent can take
| Agent | Paste, drop or pick a file | Mention a project file |
|---|---|---|
| Claude | Images, PDFs and any other file | Yes |
| Codex, OpenCode, Copilot, Pi | Images, only when the model in use says it reads them | Yes |
An agent that cannot open what you give it refuses it with a message saying
what it does open. Where nothing at all can be attached, the + button is
greyed out. Those agents cannot be given a file from outside their project, so
drag the file in from the Files tree, or mention it with @, instead.
Limits
- Up to 10 attachments per message. An eleventh is refused with Send some first.
- An image or a file can be up to 5 MB, a PDF up to 32 MB.
- Images are PNG, JPEG, GIF and WebP.
- Video, audio, archives, fonts, binaries and Office documents are refused, because no agent can read them.
Attachment tiles
Attachments sit above the box as tiles: a small preview for an image, the file’s icon otherwise, with the file name and its label under it.
- Click a tile to open the file in the editor.
- Cmd+click a tile, or drag it into the text, to put its label in your message again.
- Click the x in the corner of a tile to remove it. With the tile focused,
DeleteorBackspaceremoves it too. Removing an attachment also takes its label out of your message.
A mentioned file is checked when the chat opens and every time you click back into the box. If it has been deleted or moved, its tile turns red and says where the file was. The tile stays, because your sentence still names it; remove it yourself if the agent no longer needs it.
When you reopen a chat later, the attachments in its past messages come back with it.
Long pastes become files
A paste over 30 lines or 3,000 characters becomes an attachment named
pasted.txt instead of a wall of text in the box. The agent reads it the same
way, and your draft stays readable. Turn Attach long pastes as files off in
Settings > Chat to keep every paste inline. Only Claude takes a pasted
file, so with any other agent a long paste always stays in the box.
Write a long prompt in the editor
For a prompt that needs real editing, multiple cursors, find and replace, or vim keys, open it in the editor.
- Click the pen button on the composer’s bar, Open the draft in the editor.
- Tori writes your draft to a scratch file named
prompt-1(thenprompt-2, and so on) and opens it in an editor tab. - The composer goes read only and says Editing in prompt-1. Save there to
update this draft, or edit here. Each
Cmd+Sin the editor copies the file’s text back into the composer. - When you are done, click the check button in the editor’s bar, Done: put the prompt in the chat’s input and close it here. The text goes back into the composer, the tab closes and the chat comes forward. Or just send from the chat: it sends what the editor holds right now, saved or not, and closes the tab.
Click edit here under the composer to take the draft back without closing the editor’s text in, or close the editor tab: the composer keeps whatever you last saved. The scratch file is deleted when the link ends.
While a draft is open in the editor you cannot start editing a queued message.
Dictate a message
Click the waveform button on the composer’s bar, Dictate a message, and talk. Click it again to stop. What you said is turned into text on this Mac and put into the box at the caret, after a space unless the caret is at the start of the text or of a line. Nothing is sent: the words wait in the box like anything you typed.
Dictation needs an Apple Silicon Mac. On an Intel Mac the button is not there, and Settings > Chat > Dictation says Needs an Apple Silicon Mac.
While you talk, a waveform covers the text box and moves with your voice, with Listening. Press Escape to drop it under it, and the button turns red. Click it again, Stop and put the words in the message, to finish. The bars then stop following your voice and roll across the box, Turning what you said into text, while the model works. A click does nothing here; the text lands in a moment and the box comes back. The box takes no typing while the waveform covers it.
Escape while recording drops the recording and inserts nothing. While a
turn is running, that first Escape goes to the recording and the next one
stops the turn.
Downloading a model
The first click with no model downloaded opens Download a dictation model. Like the voice for Read aloud, it runs on your Mac and your voice never leaves it. Pick one:
| Choice | Size | What it trades |
|---|---|---|
| Moonshine | 67 MB | Small and quick. Good for a sentence or a paragraph at a time. The default |
| Parakeet | 661 MB | More accurate, and ten times the download. Parakeet by NVIDIA, CC BY 4.0 |
Download fetches it and the dialog shows the progress, Downloading, 40 of 67 MB, with Cancel download. Not now closes the dialog, and a download already running carries on. A download that fails says why, with Retry. Once it lands, click the waveform button again to record. One model is installed at a time; Change in Settings swaps it for the other, and Delete removes it. See Settings.
The first time
macOS asks whether Tori may use the microphone the first time you record. If you refuse, or turned it off later, or macOS gives Tori silence instead of sound, the recording stops within a couple of seconds and a notice says Tori isn’t getting sound from the microphone. Allow it under System Settings, Privacy & Security, Microphone.
What happens to the clip
- A clip that never rises above the room’s noise is dropped without a word: nothing is inserted and nothing is said.
- A long clip is cut at its quietest gaps into pieces under fifty seconds before the model sees it, and the pieces are joined with a space. A recording left running is cut at fifteen minutes.
- The model loads on the first use, about half a second for Moonshine and a second or two for Parakeet, and is dropped after five idle minutes.
- A transcription that fails says so in a notice, Couldn’t transcribe, with the reason.
The dictation button is greyed out while the draft is open in the editor.
Quote from the transcript
Select some text in the transcript and a small toolbar appears beside the
selection, with Quote and Read. Click Quote to put those lines
into the composer as a Markdown quote, each line starting with >, with a
blank line under it and the caret ready for your reply. Read reads the
selection aloud instead, see
Read a reply aloud. Selections
inside an input, like the free text box of a question, do not offer the
toolbar.
The same toolbar appears when you select text in a PDF. There Quote sends the quote, with the page number, to the selected session.
Things that land in the composer from elsewhere
Several parts of Tori put text into a chat for you to read and send. None of them ever presses Enter for you: the text waits in the composer, or is typed at the prompt of a terminal agent, until you send it.
| From | Goes to | What arrives |
|---|---|---|
| A comment on a hunk in a diff | The session selected in the sidebar | The file and line range, and your comment |
| A problem in the Problems panel | The selected session | The file and line, the severity and message, and any fixes the language server offers |
| A TODO in the TODOs panel | The selected session | The line, and “Fix this TODO” with its text |
| Ask in the Debug panel | The selected session | The paused frame, its call stack and the variables in scope |
| Ask agent to resolve on a merge conflict | The selected session | The conflicted file and what to resolve |
| A Quote from a PDF | The selected session | The quoted text and its page |
| The send button on a pull request thread | The session that worked on the pull request’s branch | The thread, for the agent to address |
| The chat button on a file in Settings > Agents | A new chat draft | The file, and “Write my Claude skills” or similar |
If the target session is waiting on a permission prompt, the send is refused with Session is waiting for permission, answer it first. Answer the prompt and try again.
Things that stop the composer
The composer is disabled, with the reason on the bar, when:
- The agent is turned off in Settings > Agents.
- The project only allows other agents or accounts (see Limiting agents per project).
- The agent is not installed, is signed out, or needs a program it cannot find, for example Chat needs npx.