Skip to content

EN Course 03 Message Protocol and Display

lloydzhou edited this page May 29, 2026 · 1 revision

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
}

Why There Is an Internal Protocol

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.

Why Not Print Directly

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-json must 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 Output and Stream JSON

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.

Next

Session Store explains how durable session state is organized.

Clone this wiki locally