-
Notifications
You must be signed in to change notification settings - Fork 5
EN Course 03 Message Protocol and Display
bash-agent separates model events from terminal rendering. Internal messages use a small RESP-like frame so arbitrary text, JSON, and multiline tool output can move through pipes safely.
util_write_msg() {
local nfields=$# field byte_len _out="" _seg=""
printf -v _out '*%s\r\n' "$nfields"
for field in "$@"; do
byte_len=$(LC_ALL=C printf '%s' "$field" | wc -c)
byte_len=${byte_len//[[:space:]]/}
printf -v _seg '$%s\r\n%s\r\n' "$byte_len" "$field"
_out+="$_seg"
done
printf '%s' "$_out"
}The display side reads the same frame:
display_stream() {
while util_read_msg; do
display_message
done
}The Bash runtime has clear layers, but Bash cannot pass typed structs between them. The portable boundary is a byte stream: pipes, files, stdout, and stderr.
That is why the reference implementation uses an internal RESP-like protocol between layers:
transport / parser / tools / agent loop
-> RESP-like messages
-> display / event handling
The protocol carries structured messages:
TEXT
THINKING
TOOL_CALL
TOOL_RESULT
USAGE
SUB_AGENT_RESULT
STOP
CONTEXT_UPDATE
ERROR
RETRY
RESP-like framing is length-prefixed. That matters because message fields may contain:
- multiline tool output
- JSON tool input
- text with embedded newlines
- arbitrary UTF-8 content
- empty fields
This gives Bash a reliable struct-like boundary while still using only byte streams.
Direct printf calls from every subsystem create ordering problems:
- streaming text can interleave with tool output
- sub-agent results can redraw over the prompt
-
stream-jsonmust still receive event lines - replay should reuse the same display rules as live execution
The message protocol gives the runtime a single display boundary.
Human display is handled by display_message. Machine-readable output is produced at the event layer.
store_event_append() {
[[ -n "${SESSION_EVENT_FILE:-}" ]] || return 0
if util_is_stream_json; then
printf '%s\n' "$1" | tee -a "$SESSION_EVENT_FILE"
else
printf '%s\n' "$1" >> "$SESSION_EVENT_FILE"
fi
}This means events.jsonl is always written, and stream-json is just an additional target for the same event stream.
Session Store explains how durable session state is organized.