Serve Protocol

discli serve implements a bidirectional communication protocol over stdin and stdout using newline-delimited JSON (JSONL). Each line is a single, self-contained JSON object. Events flow out on stdout; actions flow in on stdin.

flowchart LR
    subgraph Agent Process
        A[AI Agent / Script]
    end
    subgraph discli serve
        B[Event Handlers] --> C[stdout]
        D[stdin] --> E[Action Dispatcher]
    end
    subgraph Discord
        F[Discord Gateway]
    end

    F -- events --> B
    C -- JSONL events --> A
    A -- JSONL actions --> D
    E -- API calls --> F

Protocol basics

  • Transport: stdin (actions) and stdout (events), one JSON object per line
  • Encoding: UTF-8
  • Delimiter: newline (\n) — each line is a complete JSON object
  • Direction: stdout is server-to-client (events), stdin is client-to-server (actions)
  • Correlation: actions can include a req_id field; the corresponding response will echo it back
Warning

Do not write anything to stdout other than JSONL when using serve mode programmatically. Diagnostic messages are written to stderr. If you need to debug, read stderr separately.

Events (stdout)

Events are emitted by discli as things happen on Discord. Every event object has an "event" field identifying its type.

Lifecycle events

EventFieldsDescription
readybot_id, bot_nameBot connected to Discord gateway
slash_commands_syncedcount, guildsSlash commands registered with Discord
errormessageInternal error (non-fatal)
shutdownBot is shutting down

Message events

EventFieldsDescription
messagemessage_id, channel_id, channel, server, server_id, author, author_id, content, is_bot, mentions_bot, is_dm, timestamp, attachments, reply_toNew message received
message_editmessage_id, channel_id, channel, server, server_id, author, author_id, old_content, new_content, timestampMessage was edited
message_deletemessage_id, channel_id, channel, server, server_id, author, author_id, contentMessage was deleted

Interaction events

EventFieldsDescription
slash_commandcommand, args, user, user_id, channel_id, guild_id, interaction_token, is_adminSlash command invoked by a user

Reaction events

EventFieldsDescription
reaction_addemoji, user, user_id, message_id, channel_id, server, channelReaction added to a message
reaction_removeemoji, user, user_id, message_id, channel_id, server, channelReaction removed from a message

Member events

EventFieldsDescription
member_joinmember, member_id, server, server_idUser joined a server
member_removemember, member_id, server, server_idUser left or was removed from a server

Response events

Every action sent via stdin produces a response on stdout:

EventFieldsDescription
responsestatus (ok or error), req_id (if provided), plus action-specific fieldsResult of a dispatched action

Example event payloads

{
"event": "message",
"server": "My Server",
"server_id": "111111111111111111",
"channel": "general",
"channel_id": "222222222222222222",
"author": "Alice#1234",
"author_id": "333333333333333333",
"is_bot": false,
"content": "Hello bot!",
"timestamp": "2026-03-15T10:30:00+00:00",
"message_id": "444444444444444444",
"mentions_bot": true,
"is_dm": false,
"attachments": [],
"reply_to": null
}
{
"event": "slash_command",
"command": "ask",
"args": {"question": "What is discli?"},
"channel_id": "222222222222222222",
"user": "Alice#1234",
"user_id": "333333333333333333",
"guild_id": "111111111111111111",
"interaction_token": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"is_admin": false
}
{
"event": "response",
"req_id": "msg-001",
"ok": true,
"message_id": "555555555555555555"
}

Actions (stdin)

Actions are JSON objects sent to discli via stdin. Every action must have an "action" field. Optionally include "req_id" to correlate the response.

{"action": "send", "channel_id": "222222222222222222", "content": "Hello!", "req_id": "msg-001"}

Messaging

ActionRequired FieldsOptional FieldsDescription
sendchannel_id, contentfiles, req_idSend a message to a channel
replychannel_id, message_id, contentfiles, req_idReply to a specific message
editchannel_id, message_id, contentreq_idEdit a bot message
deletechannel_id, message_idreq_idDelete a message

Streaming

Streaming lets you progressively update a single message, similar to how ChatGPT streams responses. Content is buffered and flushed to Discord every 1.5 seconds to respect rate limits.

ActionRequired FieldsOptional FieldsDescription
stream_startchannel_idreply_to, interaction_token, req_idStart a streaming message (sends ”…” placeholder)
stream_chunkstream_id, contentreq_idAppend text to the stream buffer
stream_endstream_idreq_idFinalize the stream, flush remaining content
Tip

The stream_start response includes a stream_id that you must pass to subsequent stream_chunk and stream_end actions. If the content exceeds Discord’s 2000-character limit, stream_end automatically splits it across multiple messages.

Streaming example

{"action": "stream_start", "channel_id": "222222222222222222", "req_id": "s1"}
{"event": "response", "req_id": "s1", "stream_id": "a1b2c3d4", "message_id": "555555555555555555"}
{"action": "stream_chunk", "stream_id": "a1b2c3d4", "content": "Here is the "}
{"action": "stream_chunk", "stream_id": "a1b2c3d4", "content": "answer to your question..."}
{"action": "stream_end", "stream_id": "a1b2c3d4", "req_id": "s1-end"}

Interactions

ActionRequired FieldsOptional FieldsDescription
interaction_followupinteraction_token, contentreq_idSend a follow-up response to a slash command
Note

Slash command interactions are automatically deferred with a “thinking” indicator. You must respond with either interaction_followup or stream_start (with the interaction_token) within 15 minutes, per Discord’s limits.

Typing

ActionRequired FieldsOptional FieldsDescription
typing_startchannel_idreq_idStart showing “bot is typing…” indicator
typing_stopchannel_idreq_idStop the typing indicator

Presence

ActionRequired FieldsOptional FieldsDescription
presencestatus, activity_type, activity_text, req_idUpdate bot status and activity

status can be online, idle, dnd, or invisible. activity_type can be playing, watching, listening, or competing.

Reactions

ActionRequired FieldsOptional FieldsDescription
reaction_addchannel_id, message_id, emojireq_idAdd a reaction to a message
reaction_removechannel_id, message_id, emojireq_idRemove the bot’s reaction from a message

Threads

ActionRequired FieldsOptional FieldsDescription
thread_createchannel_id, namemessage_id, auto_archive_duration, content, req_idCreate a thread (optionally from a message)
thread_sendthread_id, contentfiles, req_idSend a message to a thread
thread_listchannel_idreq_idList threads in a channel

Polls

ActionRequired FieldsOptional FieldsDescription
poll_sendchannel_id, question, answersduration_hours, multiple, content, req_idCreate a poll in a channel

answers is an array of strings or objects with text and optional emoji fields. Minimum 2 answers required.

Channels

ActionRequired FieldsOptional FieldsDescription
channel_listguild_id, req_idList channels (optionally filtered by server)
channel_createguild_id, nametype, req_idCreate a channel (text, voice, or category)
channel_infochannel_idreq_idGet channel details

Members

ActionRequired FieldsOptional FieldsDescription
member_listguild_idlimit, req_idList server members (default limit: 50)
member_infoguild_id, member_idreq_idGet detailed member info including roles

Roles

ActionRequired FieldsOptional FieldsDescription
role_listguild_idreq_idList server roles
role_assignguild_id, member_id, role_idreq_idAssign a role to a member
role_removeguild_id, member_id, role_idreq_idRemove a role from a member

DMs

ActionRequired FieldsOptional FieldsDescription
dm_senduser_id, contentreq_idSend a direct message to a user

Message queries

ActionRequired FieldsOptional FieldsDescription
message_listchannel_idlimit, req_idList recent messages (default limit: 20)
message_getchannel_id, message_idreq_idGet a single message with full details
message_searchchannel_idquery, author, limit, req_idSearch messages by content or author
message_pinchannel_id, message_idreq_idPin a message
message_unpinchannel_id, message_idreq_idUnpin a message

Server

ActionRequired FieldsOptional FieldsDescription
server_listreq_idList servers the bot is in
server_infoguild_idreq_idGet detailed server info

Request-response correlation

Every action can include a req_id field (any string). The response will echo it back, letting you match responses to requests in concurrent scenarios.

{"action": "send", "channel_id": "123", "content": "Hello", "req_id": "abc-001"}
{"event": "response", "req_id": "abc-001", "ok": true, "message_id": "456"}
Tip

If you omit req_id, the response will still be emitted but without a correlation ID. For simple sequential agents this is fine. For agents that send multiple actions concurrently, always include req_id.

Error handling

When an action fails, the response includes "error" instead of "ok":

{"event": "response", "req_id": "abc-002", "error": "Channel not found: 999"}

Common error conditions:

ErrorCause
Missing 'action' fieldThe JSON object did not include an action key
Unknown action: fooThe action name is not in the action registry
Channel not found: ...Invalid or inaccessible channel ID
Missing 'guild_id'A required field was omitted
Invalid JSON: ...The stdin line was not valid JSON

Non-fatal errors (e.g., slash command sync failures) are emitted as standalone error events:

{"event": "error", "message": "Failed to sync commands to My Server: ..."}

Event filtering

Use command-line options to filter which events are forwarded:

Terminal window
# Only message and reaction events
discli serve --events messages,reactions
# Only events from a specific server
discli serve --server "My Server"
# Only events from a specific channel
discli serve --channel "#general"
# Exclude bot's own messages
discli serve --no-include-self

Available event filter values: messages, reactions, members, edits, deletes.

Slash command registration

Pass a JSON file to register slash commands on startup:

Terminal window
discli serve --slash-commands commands.json
[
{
"name": "ask",
"description": "Ask the bot a question",
"params": [
{"name": "question", "type": "string", "required": true, "description": "Your question"}
]
},
{
"name": "status",
"description": "Check bot status"
}
]

Commands are synced per-guild for instant availability. When a user invokes a slash command, a slash_command event is emitted with an interaction_token that you use to respond.

Next steps