Usage

Sending & receiving

Handlers receive a Thread and a Message (or Action). thread.post answers the inbound message that started this turn. It enqueues a command; the adapter sends it.

The message

FieldWhat it is
textPlain text. Empty when the channel withheld content (e.g. Discord without Message Content Intent).
thread_idStable id, e.g. telegram:….
chat_kinddm / group / channel.
senderWho sent it.
message_idPlatform message id — for reply, react, pin, delete, edit.
attachmentsTuple of Attachment (photo, file, …).
reply_toQuoted / replied-to message id, if any.
rawPlatform payload for power users.

post, send, and reply

Three different ways to put text on the thread. The Telegram example exposes each as a slash command (/send, /reply; everything else uses post).

MethodWhat it does
thread.post(text) Answer the inbound message that started this turn. On email this is a threaded reply (In-Reply-To / References). On Telegram/Slack it replies in the same chat/thread.
thread.send(text) A new message that is not a reply to the inbound turn. On email: no In-Reply-To, so it starts its own conversation. On Telegram: a standalone post in the chat (the example’s /send).
thread.reply(message_id, text) Quote / reply-to a specific platform message id (Telegram quote, Slack thread). Pass msg.message_id for the message that just arrived.
thread.post("answers this turn")
# email: threaded reply (In-Reply-To). Telegram/Slack: same chat.

thread.send("standalone — not a reply to your message")
# new conversation on email; not attached to the inbound turn

thread.reply(msg.message_id, "quoted.")
# reply-to that specific message id (Telegram quote)

thread.post("with buttons", actions=(
    Button(label="ok", data="ok"),
    Button(label="docs", url="https://example.com"),
))
await thread.post("answers this turn")
await thread.send("standalone — not a reply to your message")
await thread.reply(msg.message_id, "quoted.")
await thread.post("with buttons", {
  actions: [
    { label: "ok", data: "ok" },
    { label: "docs", url: "https://example.com" },
  ],
})

Turn-taking

Method
typing()Typing indicator where the channel has one.
edit(message_id, text)Edit a sent message.
delete(message_id)Delete.
react(message_id, emoji)Add a reaction.
pin / unpinPin in groups (Telegram pin in DMs is a no-op with a hint).
forward(to_thread_id, message_id)Forward.
send_media(attachment, caption=…)Photo/file by URL or adapter attachment.
send_blocks(blocks, text=…)Rich layout; adapters degrade (Telegram → text + keyboard).
initiate(text)Cold-DM shape; maps to send on channels that allow it.
schedule(text, send_at)Send later.

Need a native method the core does not name (Telegram sendPhoto with extra flags)? Call the adapter / caspian catalog get telegram.send-photo. The bot program never names sendMessage. TypeScript thread methods are camelCase (sendMedia, sendBlocks, markRead); snake_case aliases exist.

Acknowledgement (ack)

ack is an acknowledgement: an instant reply sent before the handler runs, so the human sees a line while the model is still thinking. Channels with no typing indicator (email, SMS, X) need this; Discord/Telegram can use thread.typing() instead.

This is not the same as the adapter acknowledging a button press (that always happens so the spinner stops). ack is a user-visible message on the thread.

@cx.on_message({"channel": "email", "ack": "On it…"})
def handle(thread, msg, ctx):
    thread.post(slow_answer(msg.text))
    # human already got "On it…" ; this post is the real answer
cx.onMessage({ channel: "email", ack: "On it…" }, async (thread, msg) => {
  await thread.post(await slowAnswer(msg.text))
  // human already got "On it…" ; this post is the real answer
})