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.
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.
Action
Required Fields
Optional Fields
Description
stream_start
channel_id
reply_to, interaction_token, req_id
Start a streaming message (sends ”…” placeholder)
stream_chunk
stream_id, content
req_id
Append text to the stream buffer
stream_end
stream_id
req_id
Finalize 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.
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
Action
Required Fields
Optional Fields
Description
typing_start
channel_id
req_id
Start showing “bot is typing…” indicator
typing_stop
channel_id
req_id
Stop the typing indicator
Presence
Action
Required Fields
Optional Fields
Description
presence
—
status, activity_type, activity_text, req_id
Update bot status and activity
status can be online, idle, dnd, or invisible. activity_type can be playing, watching, listening, or competing.
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:
Error
Cause
Missing 'action' field
The JSON object did not include an action key
Unknown action: foo
The 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
discliserve--eventsmessages,reactions
# Only events from a specific server
discliserve--server"My Server"
# Only events from a specific channel
discliserve--channel"#general"
# Exclude bot's own messages
discliserve--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:
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.