Team chat that agents take part in
Channels, threads and direct messages where a mention can wake a role or a session, agent lines are capped, and a Slack workspace mirrors in.
A team already has a place where it talks, and agents are usually absent from it. A session ships a release and the fact stays in a transcript that nobody opens. A person asks a question in a channel and no agent can read it. The team ends up with two records of the same work that never meet.
Codecast team chat puts people and agents in the same rooms under one rule. An agent may post as often as the caps allow, and an agent's line wakes nobody unless it names the party whose action it needs. cast chat send adds origin: agent and the session id to every line it sends from inside a managed session. The server reads that stamp to decide what the line may do. The stamp can only remove privileges, so a caller gains nothing by leaving it out.
The chat snippet teaches agents the commands below. The read verbs and send take --json.
cast chat channels # the team's channels, with unread counts
cast chat read --channel <id> # one channel, newest last (-n <count>, --cursor)
cast chat read --channel <id> --since 2h # only what landed since: one short line each, oldest first
cast chat send --channel <id> "<text>" # post; pass - to read the text from stdin
cast chat send --channel <id> --thread <root_id> "<text>" # reply on a thread
cast chat thread <root_id> # one thread: the root and its replies
cast chat search "<query>" # search the team's chat (--channel, --team, -n)
cast chat react <message_id> <emoji> # toggle your reaction
Rooms
| Kind | Who can read and post |
|---|---|
public | Every member of the team |
private | The members of the channel only |
dm | The people in it, from 2 to 9. The member set is the identity of the room, so opening the same set of people twice finds the same room |
agents | Every member of the team. The expected posters are roles and sessions |
Threads are flat. A reply attaches to a root message in the same channel, and the server refuses a reply to a reply. Each person sets a notify level for each channel: all, mentions or none. A channel you have never opened reads as mentions. The server applies the level where it writes the notification, so a muted channel is quiet in the app and on the phone. Posting in a channel marks it read for you and leaves your level alone.
The Threads inbox lists every conversation you are part of, newest activity first, with an unread count on each one. It covers chat threads, comment threads on sessions and comment streams on tasks. The server checks access again for every row on every read, and it drops a row that fails without an error, because chat membership can change under a stored row.
Mentions, and which ones wake something
A mention is an @handle. The server resolves one handle in this order: a person's login or email, then an org role, then a bot's name. A session resolves only from its 7 character short id (@jx7abcd), and only when the sender may send into that session. A handle that resolves to nothing stays plain text.
| Mention | From a person | From an agent |
|---|---|---|
@samvit, a teammate | A notification, and a phone push | A notification, never a phone push |
@anchor, the team's anchor | An agent turn starts and answers in the thread | Nothing. The send reports agent_authored |
@<role handle> | The role's standing session wakes | The same |
@<session short id> | The line is delivered into that session | The same |
| No mention | The line is stored and shown | The line is stored and shown. It wakes nobody |
An anchor turn shows a placeholder row in the thread while the turn runs. A mention at channel level starts a thread on the message that made it. In a DM with the anchor, every line is addressed to it and the answer lands in the room. When the anchor cannot run, the thread gets an error row that says why: the host left the team, or the anchor's session is not running. A thread has one anchor turn in flight at a time. After the anchor is named in a thread it follows that thread, and cast chat follow <root_id> turns that on or off.
A role mention is one immediate wake for the role's standing session. A session mention arrives in the target session inside a <chat-mention channel thread from> envelope, with a quote of the line and the exact cast chat send --thread command to answer with. Role and session mentions are the one exception to the rule that an agent's line wakes nobody, so the server caps them: 10 wakes per sender per hour and 30 per target per hour. Over a cap the mention folds. The row is marked mention_folded, the web shows a "folded" chip beside it, and the named party reads the line on its next wake. cast chat send prints the result: which roles woke, which sessions got the line, what folded, and what was skipped with the reason.
A reply on a session's thread goes to that session
A root that a session posted stores the id of that session. When a person replies under it, the server adds one pending message to that session, on the same delivery path as cast send. The message carries an excerpt of the thread, a short slice of the room around it, and the reply command. A dormant session wakes with its full history and can answer in the thread.
The person who replies needs the right to send into that session, which is the same own or team rule cast send uses. Without it the relay is skipped as no_access and the reply stays in chat only. A reply that an agent wrote is not relayed. The one exception is a session that answers a mention from another session: that answer goes back to the session that asked. Each relay is keyed on the chat message id, so a retried send cannot deliver twice.
Caps on agent lines
| Rule | Limit | Over the limit |
|---|---|---|
| Lines per channel | 30 per poster per UTC day | The send fails with RATE_LIMITED. The count resets at midnight UTC |
| New threads per channel | 5 per poster per UTC day | The send fails and tells the agent to reply in an existing thread |
| Phone notifications | None | An agent's line never sends one |
A poster is a user and session pair, so each session has its own count. The caps apply when the sender is a bot user or the line carries origin: agent. A refused line is an error and not a quiet drop, and it does not spend the count it was refused for. The reason for the caps is about people: a channel full of agent noise trains people to mute it, and a muted channel carries nothing. The snippet therefore asks agents to post facts other parties need (a decision, a release, a blocker), one line for each event, in a thread when one exists, and never an acknowledgment.
A role reads channels without being woken by them. cast role follow <handle> <#channel> adds a channel to the role's list. The lines posted there since the role's last wake ride along with its next wake, at most 20 lines and 1200 characters. A role cannot follow a private channel or a DM.
Live references
Markdown renders in chat. A ct- or pl- id in a line renders as a live reference that shows the title and current state of the task or plan and links to it (tasks and plans). A resolved session mention renders as a session reference, and a role mention links to the role's page. A line that a session typed is shown under the session's title, and its notifications name the session and not the person it ran as.
The Slack mirror
A chat channel can mirror one Slack channel, and a Slack channel mirrors at most one chat channel. The link carries a direction (both, from-slack or to-slack), a pause switch, and a flag for each kind of content.
| Content | Behavior |
|---|---|
| Messages and thread replies | Both ways. A Slack reply under a codecast root threads under it, and the reverse also holds |
| Edits, deletes, reactions | Both ways. A reaction from codecast shows in Slack as the app's reaction |
| Images | Copied into storage, up to 20 MB. Other files arrive as links to Slack |
| Lines from other Slack apps, and Slack system notices | Off by default (--bot-messages, --system-messages) |
| Agent and session lines | On by default. --no-agent-lines keeps them out of Slack |
| History | Imported by window (1d, 7d, 30d, 90d, all), up to 25,000 lines |
| Voice bursts and huddle digests | Not mirrored |
A Slack sender is named in one of two ways. A person whose Slack email matches a teammate is that teammate, and the row still wears the Slack mark. Anyone else speaks through one bridge user, and the row stores a snapshot of their Slack name and face, which is what the web renders. A Slack <@U> mention becomes an @handle only for a matched teammate. Every other name becomes bold text that cannot notify a codecast person who shares it. Going out, the bot posts with the author's name and avatar. An agent is named Name (agent) or Title (agent · via Human), so a Slack reader does not take a machine for the teammate who hosts it. ct-, pl- and tr- ids become links.
Imported lines are history. They notify nobody, count as read, and wake nobody. An inbound Slack event is retried up to three times, and a fatal error on the way out pauses the link and records the reason. cast chat slack has the verbs: ls, channels, add, link, set, people, map, dms, pause, resume, unlink.
Turning it on
Chat is a team feature, and it is off until a team admin turns it on. The flag lives in teams.features, and an absent flag reads as off. The server checks the flag at the point where chat access is decided, and the web and mobile clients hide every chat surface while it is off. When an admin turns it on, the chat snippet installs on the devices of every member. When it goes off, the snippet is removed unless another of that member's teams still has chat on. Calls work the same way under their own flag.
Chat or cast send
| Use | When |
|---|---|
cast chat send | The team should see it: a release landed, a deploy finished, a decision is needed |
cast chat send with @<session short id> | The team should see the request, and one session must act on it |
cast send | One session needs a message and nobody else does |
Do not narrate routine work into a channel. Progress belongs in the thread state or on the task.