Skip to main content

The code channel lifecycle

Overview​

This guide walks through a code channel's life from creation to archive in detail, from the perspective of your app. A high-level overview of a typical agent working session can look like this:

  1. The user mentions your agent (@YourAgent migrate the billing cron).
  2. Your app receives an app_mention event, recognizes multi-step work, and calls the agents.conversations.create API method with the original channel and message timestamp automatically, from the request and its context, without waiting to be asked.
  3. Slack creates the channel, sets privacy to match the origin channel, and allows origin channel members to join.
  4. Working session: your app calls the agents.sessions.setStatus (session status), agents.conversations.setProperties (context bar items), and agents.conversations.setView (diff, HTML, Block Kit, canvases) API methods, replies with the chat.postMessage API method, and receives message / app_mention events as the user participates. Users talk to your app the way they'd talk to a teammate. Your app replies in the channel, not in a thread, and responds to relevant messages even when they aren't @-mentioned. Your app can call the agents.conversations.setProperties API method with code_channel.context_bar_items to pin up to five items: repo, branch, PR link, CI status, token usage, etc. to the top of the channel and update them as the work progresses, and can also call the agents.conversations.setView API method with type: "diff" and a unified diff to populate the channel's code tab. If the user stops the agent, your app receives an agent_session_stopped event and ceases all activity for the session immediately.
  5. Your app posts a final summary with the chat.postMessage API method.
  6. Your app calls the agents.conversations.archive API method with summary_message_ts.
  7. Slack records the summary as the channel's summary_message property and archives the channel.

1. Creation​

Deciding whether to open a code channel​

The short version: follow Slack's user principles and don't make the user think. Whether a request becomes a code channel is your agent's call, made from the prompt and the context around it. If a request implies multi-step work, such as changing code, running builds or tests, opening a PR, or iterating with the requester, open a channel for it automatically. Don't require the user to ask for a code channel, or to know a command that creates one. Reserve in-place replies for what fits in a single message.

Opening a channel is cheap and reversible: it's semi-ephemeral, members of the origin channel can join, its origin_link points back at the triggering message, and you archive it with a summary when the work is done. The question isn't whether a task is important enough to deserve its own channel, it's whether the work will take more than one message to complete.

Open a code channel when the work will:​

  • take multiple steps or need back-and-forth with the requester, i.e., clarifying scope, reviewing options, iterating on results
  • create or modify code, run builds or tests, or open a pull request
  • produce progress updates or long output that would crowd the thread
  • be explicitly requested as a session, task, or working space

Reply in place when the request is:​

  • answerable in a single message, such as a question, a lookup, a short explanation, or a quick review comment
  • about the surrounding conversation rather than about making changes
  • something the people in the thread will want to see without leaving it

When it's genuinely ambiguous, reply in place and offer to go deeper in a code channel. If the mention already arrives inside a code channel, don't create another one; continue the session there (refer to receiving an app_mention inside a code channel you didn't create).

Instructing your agent​

This decision is better made by the model reading the request than by a keyword check in front of it. If your agent is prompt-driven, the use the text in the section above as a starting point, adapting the wording to your own product.

Example​

A typical example is as follows:

POST /api/agents.conversations.create
{
"name": "Migrate billing cron to Temporal",
"session_id": "ses_8675309",
"origin_channel_id": "C0123456789",
"origin_message_ts": "1717171717.123456"
}

Because you pass the origin link and omit is_private, Slack automatically:

  • sets the code channel's privacy to match the origin channel.
  • allows members of the origin channel to join the code channel.
  • records the origin link as the channel's origin_link property, and names the channel from the origin message if you omit name.

The session_id property is an idempotency key: retrying the call with the same session_id returns the existing channel rather than creating another channel.

Your app can create a code channel from any message; the user doesn't need to have mentioned you. However, the most common pattern is responding to a mention by creating a space for deeper work.

Receiving an app_mention inside a code channel you didn't create​

Under some circumstances, your app may receive an app_mention event from a channel that is already a code channel, meaning that your agent has been added to an existing one. This happens when a user started a code channel from a message your agent was not present for (e.g., via Slack's ephemeral prompt in a DM when the agent is not yet in the channel), or when they created the channel directly from Slack's own code channel UI.

To investigate, call the conversations.info API method and check properties.record_channel.record_type === "agent_channel". That block is written at creation and is on every code channel, which makes it the reliable signal even for a channel whose session hasn't produced anything yet. Don't gate on properties.code_channel having content; a channel created moments ago has an empty {}. Refer to code channel properties for more details.

Once you've confirmed it, treat the mention as the start of a session. Read the origin link and session state from the same conversations.info API method response, then begin work normally.

2. Working in the channel​

Throughout the session, your app keeps the channel in sync. All of the calls below are made with your bot token against the code channel.

Respond in-channel, and don't wait to be mentioned.

Code channels behave differently from general-purpose channels where a bot is one of many participants. Two conventions many Slack apps are built around do not apply here:

  • Reply in channel, not in thread. Post your responses as top-level messages in the channel (omit thread_ts) so the conversation reads as a single flowing session. Only thread a reply when the user threaded first, or when you're answering a specific earlier message. Defaulting every reply in a thread fragments the session and hides your agent's work.
  • Respond even when you aren't @-mentioned. In a code channel the user is talking to your agent; they shouldn't have to @-mention it on every message. Treat messages directed at the agent as turns to act on, not just explicit mentions. (Subscribe to the app_mention, message.session or message.channels / message.groups events, so that you receive them.) Use judgement: a user clearly talking to a human teammate doesn't require a response.

Streaming messages: The chat.startStream API method supports top-level streaming messages in code channels. Omit thread_ts to post a top-level streaming message instead of a thread reply, matching the recommended pattern of keeping the session as one continuous conversation. Only pass a real thread_ts when replying inside a specific thread the user started. Passing thread_ts: "0" does the same thing and is still accepted, so existing integrations keep working.

On session start: set status to processing while the agent spins up:

POST /api/agents.sessions.setStatus
{
"channel_id": "C9876543210",
"status": "processing"
}

Then populate the context bar with the basics:

POST /api/agents.conversations.setProperties
{
"channel_id": "C9876543210",
"code_channel": {
"context_bar_items": [
{ "key": "repo", "label": "borant/billing", "icon": "folder", "url": "https://github.com/borant/billing" },
{ "key": "branch", "label": "agent/migrate-cron", "icon": "branch", "url": "https://github.com/borant/billing/tree/agent/migrate-cron" }
]
}
}

While working: flip status between processing while the agent is computing and active when it finishes a turn with nothing outstanding. If you post a question and need an answer before you can continue, set the status as suspended rather than active.

Update the diff after meaningful changes:

POST /api/agents.conversations.setView
{
"channel_id": "C9876543210",
"type": "diff",
"content": "diff --git a/cron.py b/cron.py\n--- a/cron.py\n+++ b/cron.py\n@@ ...",
"base_branch": "main",
"head_branch": "agent/migrate-cron"
}

The agents.conversations.setView API method call is an upsert: the first call creates the backing file and channel tab; later calls replace the content and bump content_version.

When a PR opens: add it to the context bar (remember the array replaces the current set, so resend the items you want to keep):

POST /api/agents.conversations.setProperties
{
"channel_id": "C9876543210",
"code_channel": {
"context_bar_items": [
{ "key": "repo", "label": "borant/billing", "icon": "folder", "url": "https://github.com/borant/billing" },
{ "key": "branch", "label": "agent/migrate-cron", "icon": "branch", "url": "https://github.com/borant/billing/tree/agent/migrate-cron" },
{ "key": "pr", "label": "PR #42 is open", "icon": "hierarchy", "url": "https://github.com/borant/billing/pull/42" },
{ "key": "ci", "label": "Tests pending", "icon": "terminal" }
]
}
}

Keep the labels current as state changes. For example: "PR #42 is merged", "Tests passing", so the context bar always reflects where the work stands.

Richer artifacts: use the agents.conversations.setView API method with different types to attach views as tabs: type: "canvas" for plan documents (use access_level: "comment" to keep your agent the sole author while users comment), type: "block_kit" for structured surfaces, or type: "html" for live HTML output (dashboards, reports, previews). Each view is keyed by a stable view_key.

Renaming: if the session's focus changes, call the agents.sessions.rename API method. For a code channel, it updates both the channel name and the session title.

At any point during the session, your app may receive an agent_session_stopped event for the code channel (or a thread within it). This signals that your agent should immediately stop any in-progress work in that channel or thread. Common triggers include the user cancelling the agent's current task or Slack terminating a runaway session.

Subscribe to this event in your app manifest:

event_subscriptions:
bot_events:
- agent_session_stopped

When you receive this event:

  1. Stop any active processing for the given channel/thread.
  2. Do not post further messages or call any agents.sessions.* or agents.conversations.* API methods for that context. Slack will update the channel's session status automatically.
{
"token": "XXYYZZ",
"team_id": "T123ABC456",
"api_app_id": "A123ABC456",
"event": {
"type": "agent_session_stopped",
"channel": "C015187PM9Q",
"thread_ts": "1784744767.192879",
"user": "U013ALR8K3L",
"streaming_message_ts": [
"1784744784.686639"
],
"event_ts": "1784744788.887216"
},
"type": "event_callback",
"event_id": "Ev123ABC456",
"event_time": 1784744788
}

The streaming_message_ts property lists the timestamps of all streaming messages in progress belonging to your app in this thread session. The thread_ts property is present when the stop applies to a specific thread rather than the entire channel. If omitted, the agent should stop all work in the channel.

3. Archive​

When the work is finished (PR merged, task abandoned, user asks to wrap up, etc.), archive the channel:

POST /api/agents.conversations.archive
{
"channel_id": "C9876543210",
"summary_message_ts": "1717182000.456789"
}
  • The channel is archived for all members. Its history remains readable.
  • If you pass summary_message_ts, the timestamp of a message your agent posted in the code channel (e.g., a final summary) and Slack records it as the channel's summary_message property before archiving so the summary remains discoverable after the session ends.
  • Archiving a channel that's already archived returns already_archived.

After archiving, the code channel remains viewable: its full message history, views, and context bar are preserved. Archived code channels are indexed in search, so users can find past sessions. If needed, an archived code channel can be reopened.

A common pattern: post a wrap-up summary message in the channel, offer the user an archive action (e.g., a button), and call archive with the summary's ts once they confirm. Channels are created with status active. Setting closed does not archive the channel; call archive instead.