A Slack thread dataset should hold one thread per record. Group messages on thread_ts, then store the parent and every reply in time order. Each turn keeps a pseudonym, a timestamp, a kind and the de-identified text. Messages outside any thread get grouped by channel and day. Edits, deletions, bot posts and file links each need a stated rule.
This guide maps Slack's raw message fields onto a conversation record, line by line. It uses the field names of our JSONL conversation schema. The broader spec for chat data, including size drivers and uses, is on the workplace chat dataset page.
What does one Slack thread record look like?
One record is one complete exchange. A thread is a parent message plus the replies posted under it. In the file, each thread sits on its own JSONL line.
Here's a short fictional record. No real workspace, person or company is behind it.
{"schema": "aidb.conversation/1.0", "id": "c-0412", "source": {"system": "slack", "kind": "chat_thread", "channel": "public_channel"},
"language": "en", "labels": {"channel_type": "public", "channel_purpose": "incidents"},
"turns": [
{"n": 1, "at": "2024-02-12T09:03:00Z", "from": "<PER_1>", "kind": "message", "text": "Checkout errors since the 09:00 deploy. Anyone on it?"},
{"n": 2, "at": "2024-02-12T09:05:00Z", "from": "<PER_2>", "kind": "reply", "reply_to": 1, "text": "Rolling back now."},
{"n": 3, "at": "2024-02-12T09:11:00Z", "from": "<PER_2>", "kind": "reply", "reply_to": 1, "text": "Rolled back, errors gone.", "reactions": {"white_check_mark": 3}}
]}
The record is shortened. A full delivery also carries period, participants, outcome, deid and provenance blocks.
How do you group Slack messages into threads?
Use thread_ts. It's the field that ties a reply to its parent. Slack's developer docs give the rule in plain terms: when a message's thread_ts equals its ts, it's a parent. When they differ, it's a reply (Slack's guide to retrieving messages).
So the grouping key is simple. Take every message with a thread_ts, bucket them by that value within one channel, and sort each bucket by ts. The parent becomes turn 1.
Watch for broadcast replies
A reply can also be sent to the channel. Slack marks it with the subtype thread_broadcast. It lives in the thread and shows up in the channel too. Keep it once, inside its thread, or the record will count the same turn twice.
Decide what to do with loose messages
Not every team uses threads. Some channels are a running stream of short posts. For those, the fallback unit is a channel-day: every message in one channel on one calendar day.
Whichever you pick, say so in the datasheet. A buyer comparing two deliveries needs to know if one is threads only and the other mixes in channel-days.
Threads that run for days
Some threads stay open for a week. A reply on Friday to a question from Monday still belongs to Monday's thread. So group on thread_ts first and only then look at dates. The record's period runs from the parent to the last reply.
The same rule protects channel-days. If a reply lands the next morning, it stays with its parent. It doesn't start a new record.
Keep the channel boundary
Never merge threads across channels, even if timestamps line up. A thread belongs to one channel. Its channel_type label (public, private, shared or direct) travels with it.
Which Slack fields should the dataset keep?
Keep what helps a model follow the exchange. Drop what only identifies people or points back into the workspace.
| Slack field | Record field | Rule |
|---|---|---|
ts |
turns[].at |
Keep, as ISO 8601 UTC with the dataset's date offset |
thread_ts |
grouping key | Use to build the record, then drop |
user |
turns[].from |
Replace with a typed token such as <PER_2> |
text |
turns[].text |
Keep, after de-identification of names and mentions |
subtype |
turns[].kind, turns[].bot |
Map to message, reply or event; flag bot posts |
reactions |
turns[].reactions |
Keep counts per emoji, drop who reacted |
files |
turns[].attachments |
Keep file type and size only |
| channel name | labels.channel_purpose |
Generalise, for example "incidents" |
Two of these need a closer look. First, files. Slack's help page says JSON exports hold links to files, not the files (how to read Slack data exports). Those links can point at private content and stop working later. So the record keeps a stub with type and size, never the link.
Second, users.json. It ships with the export and lists every member's profile. It's the best source for the supplier's list of names to replace. But none of it belongs in the record. Only a generalised role, such as "support engineer", goes into participants.

How should edits, deletions and bots be handled?
Each of these shows up as a message subtype. Each needs a rule that you can check in a sample.
- Edits. Slack records an edit with the subtype
message_changedand the earlier version underprevious, if the export includes edit logs. Keep the final text only. A flag on the turn can say it was edited. - Deletions. A deleted message appears as
message_deleted, again only if retention settings kept it. Leave deleted content out. Someone chose to take it back, and that choice should hold in the dataset. - Bots. Posts from integrations use
bot_message. Setbot: trueand keep them only where they carry context, like a monitoring alert that starts an incident thread. - Channel events. Joins, leaves, renames and pins (
channel_joinand friends) becomekind: "event"turns, or get dropped. Say which.
What makes a Slack thread useful for training?
A useful thread has a question, some back and forth, and an ending. The ending matters most. A thread that closes with a fix, a decision or a hand-off can carry an outcome, which turns it into a task with a result.
Run these checks on every delivery before you accept it:
- Every record has at least two participants and the turn floor you set.
- Turn numbers start at 1 and timestamps rise inside each record.
- Every
reply_topoints at an earlier turn in the same record. - No
thread_broadcastreply appears twice. - The bot share stays under the agreed limit.
- No raw @mentions, display names, emails or phone numbers remain in
text.
The sixth check is where chat data fails most often. Names hide in greetings, nicknames and custom emoji. The de-identification checklist lists what to test in a sample and what evidence to ask for.
Threads that pass these checks work well for supervised fine-tuning on multi-party dialogue. They also work as held-out evals for summarising or answering questions about a discussion. And they can serve as the inbox an agent works from, as shown in our guide to RL environments built from enterprise workflows.
What should you ask a supplier about a Slack source?
Ask before you plan the training run, not after delivery. These five questions catch most surprises.
- Which export was run? A public-channels export can't contain private channels or DMs. Only an all-conversations export can.
- What share of messages sit inside threads? A workspace with few threads will mostly give you channel-days.
- How were broadcast replies, edits and deletions handled? Ask for the rule, then check it in the sample.
- How was the name list built? It should start from the member directory and include nicknames and handles.
- Where is it documented? The answers belong in the dataset's datasheet, next to the licence id.
The licence side of the same conversation, including due diligence steps and timelines, is covered in how AI labs procure enterprise data.
