Skip to main content
Agents talk to humans through one provider-neutral tool family: chat.*. The same seven tools send messages, read history, download attachments, react, and manage issues across Slack, Discord, Telegram, and Linear — the provider is just a field in the target. This page documents every tool and the addressing model they share. Read it when you are writing prompts that tell an agent how to reply, or deciding which connections an agent needs.

Enabling chat tools

chat.* tools register on a session when the agent declares a kind: local, implementation: chat tool. The tool’s auth binds one connection or several providers under one alias:
.auto/agents/assistant.yaml
Use auth: { kind: connection, provider: slack, connection: slack } for a single provider. The declared connections are the complete universe the tools can reach: target resolution matches the call’s target against them, and a project must be authorized to use the underlying connection grant. See connections and identities for how connections are created. Tool names as the harness sees them are flattened — claude-code renders chat.send as mcp__auto__chat_send (see Auto MCP).

Addressing: targets, destinations, recipients

Every tool that touches a conversation takes a target — a provider-discriminated address:
  • provider"slack", "discord", "telegram", or "linear".
  • destination — where in the provider: a channel plus optional thread. Per provider:
    • Slack: workspace?, channel? (id or name, # optional), thread?.
    • Discord: workspace? (connection or guild name), channel? (snowflake id or #name), thread? (the canonical discord:<guildId>:<channelId>:<threadId> id from a prior send/history result, or a bare thread id together with channel).
    • Telegram: workspace? (the connection name), channel? (chat id such as "-1001234" or a known group title), thread?.
    • Linear: workspace?, team?, issue? (issue comment threads), thread?.
  • to — a direct-message recipient instead of a destination: { kind: "user", user: "<provider user id>" } (Slack additionally supports { kind: "userGroup" }). Telegram users must have started the bot first; Discord users must share a guild with the installed bot.
Thread ids are canonical strings prefixed with their provider (slack:…, discord:…) — always pass them back exactly as a prior tool result or trigger delivery rendered them. The special selector "current" is accepted where a thread is expected. target is optional on chat.send: with it omitted, the call resolves against the session’s declared connections and succeeds only when exactly one matches — a session with a single Slack connection can just send, while a multi-provider session must name the provider (an ambiguous target is rejected with an error, not guessed). destination.workspace narrows by connection name or account name when one provider has several connections.

Message content

Message bodies are a plain string, or an explicit format wrapper:

The tools

chat.send

Send a message. Returns the address actually used plus provider ids you should keep. Output: { address, messageId, threadId, provider }. Save threadId — a send may open a new thread, and the returned id is the canonical handle for replies, reactions, and history. Sends are attributed: the platform resolves the agent’s per-agent persona (display name, avatar, bot identity from the agent’s identity) and posts as it. After an attributed send, the platform automatically records a thread-continuation binding keyed on the returned threadId and subscribes provider ingest for it — so unmentioned replies in a thread the agent opened can route back to the same session through a chat.message.subscribed trigger, with no explicit auto.bind call. (Declaring the matching trigger is still the agent author’s job; see Slack events.)
Search the session’s available workspaces, channels, and users. Each result includes a ready-made target you can pass straight to chat.send or chat.history. Search results are returned for Slack and Discord workspaces today; warnings reports providers that could not be searched.
Do not search just to resolve a Slack channel id — chat.send and chat.history accept Slack channel names directly, with or without #.

chat.history

Read recent messages from a channel or thread. Each message carries messageId, threadId, text, author, and — when files were attached — attachments metadata: { name?, mimeType?, size?, fileId?, url? }. The url is a provider URL that generally requires provider credentials; treat it as a reference, and use chat.attachment_download to actually fetch bytes.

chat.attachment_download

Turn an attachment reference into a short-lived signed URL the agent can curl from its sandbox. The platform fetches the bytes server-side through the provider connection (Slack url_private with the bot token), stores them privately, and mints the URL — provider credentials never reach the sandbox. Output: { downloadUrl, expiresAt, mimeType, size, filename }. The URL expires 5 minutes after minting — fetch it in the same turn. Allowed types and per-category size caps: images (png, jpeg, gif, webp) and documents (pdf, markdown, plain text) up to 20 MB; archives (zip, gzip, tar) up to 100 MB; video (mp4, quicktime, webm) up to 200 MB. Over-cap or unsupported types fail with an error naming the limit.
Attachment download is implemented for Slack targets today; other providers’ attachments surface as metadata only.

chat.react

Add an emoji reaction — the cheapest acknowledgement an agent can give. Output: { added, emoji, messageId, threadId }. Reactions are attributed to the agent’s persona bot.

chat.issue.get

Read issue metadata through a chat provider connection. Pass a linear target naming the issue:
Output is the full issue: id, identifier, title, description, url, priority + priorityLabel, team, state, assignee, parent/children, project, labels, timestamps.

chat.issue.update

Mutate issue metadata on the same target shape: Output returns the updated issue plus an updated report of which fields actually changed — an update that names an already-set state reports state: false.

Threading semantics

The rules that make multi-turn conversations work:
  1. Thread ids are canonical and provider-prefixed. Whatever a trigger delivery, chat.send, or chat.history gives you (slack:…, discord:…) is the id to pass back. Tools validate that a thread id matches its provider and reject mismatches.
  2. A reply is a send with destination.thread set. To start a thread from a channel message, use that message’s id as the thread root.
  3. Continuation is a binding, not magic. Events route to sessions through bindings. A platform-delivered mention typically binds its thread to the spawned session automatically; an attributed chat.send that opens a new thread binds it too. For any other thread the agent wants follow-ups from, it calls auto.bind with a slack.thread target (the target type covers both Slack and Discord threads). The agent must also declare a trigger for chat.message.subscribed (or chat.message.mentioned) events for the delivery to actually reach it — see triggers.
  4. Sends and reactions are persona-attributed, and messages authored by the agent itself are flagged in event payloads ($.auto.authored) so trigger filters can keep an agent from replying to itself.

A complete example

The messaging heart of a conversational agent, adapted from a production configuration:
.auto/agents/assistant.yaml
In a session spawned by that first trigger, the agent replies with:
and every later reply in that thread arrives as a new delivery to the same session.