Appearance
UI Streaming and Rendering
How TUI and web render harness events, and how to debug “stuck” or garbled UI.
Stream Normalization
Core streaming sanitization removes common malformed glyph patterns from token deltas before UI rendering.
Event Ordering
High-throughput streams can interleave text and structural events (tool cards, traces, retries).
Mitigation:
- queue text/trace deltas briefly
- flush buffered deltas before structural events (
tool_start,tool_result, etc.) - maintain channel separation (
uservstrace)
Web: SSE and busy state
Client: packages/web/client/useSSE.ts. Server: packages/web/server/sse.ts.
Transport vs API vs HUD “STANDBY”
Treat these separately:
| Layer | Signal | Typical meaning |
|---|---|---|
REST /api/status | busy, lastTurnEndedAt, clients | Express up; harness may be mid-turn even when SSE hiccups. |
SSE /api/stream | connected, sseTransport, sseTransportDetail | Live event delivery; reconnects briefly during API restarts or web:dev proxy ECONNRESET. |
| HUD orb “STANDBY” | idle, not necessarily broken | Idle between turns — not diagnostics for crashes. |
Client behaviour (web):
- CONNECTING / WAIT API appears before the first successful SSE handshake (
sseHasOpenedOnce); REST may already be reachable. - DEGRADED means
/api/statusOK while the EventSource layer isreconnecting/offline, including when the harness isbusy(copy notes that events may pause). - OFFLINE means
/api/statuscould not be reached — kill:3001, wrong proxy target, VPN, firewall, etc. - Busy stall watchdog: if
busyand there are no streamed assistant/tool events (tokens, tool cards, subtask lines, etc.) for ~52s, a[UI]trace fires; it repeats about every 4 minutes while the stall continues. Periodicharness_runningalone does not reset this timer — it only proves the SSE socket is warm. Use RAW for trace lines whenAGENT_UI_VERBOSITY=quiet. web:dev(Vite on:5173proxying to Express:3001) often logshttp proxy error ... ECONNRESETwhen Express restarts; the UI probes/api/statuson SSE interruption so SIGNAL does not jump straight to misleading OFFLINE when the API is still healthy.
turn_end and “stuck processing”
Symptom: trace shows [UI] Server reports idle — cleared stuck "processing" or the composer stays disabled after a long turn.
Causes:
- Very large RAW transcripts delaying
turn_enddelivery - SSE reconnect missing a single
turn_endevent - Heavy parallel tools (e.g. several
web_fetchor hungread_artifactattempts)
Mitigations (client):
- Track expected
turn_endafter each user send - Poll
GET /api/statuswith consecutive idle samples + grace period before forcing idle - Compare server
lastTurnEndedAt(set onturn_endinagentBridge) before unlocking UI
Mitigations (operator):
- Rebuild
core/toolsand restart web after harness changes - Avoid calling
read_artifactwhen distill/elide are off — see Troubleshooting - Cap parallel slow
web_fetch— see Harness protocol — Web research
Session bootstrap
GET /api/config, POST /api/message, and POST /api/persona/bootstrap await whenSessionReady() so early requests do not race harness init. See Web API.
TUI Rendering Model
Reducer-driven message list, bounded window, stable modal heights — packages/tui/src/useAgent.ts.
Common Artifact Patterns
- Channel bleed: trace/user text inside tool lines
- Fragment splits: malformed glyph separators
- Repaint flicker: frequent tiny updates
- Stale tool cards:
tool_startwithout matchingtool_resultafter crash — refresh session
Debug flow
- Confirm artifact origin (model text vs UI composition)
- Inspect SSE event sequence around corruption (
RAWtoggle in web) - Verify
turn_endand/api/statusalignment - Test with
AGENT_UI_VERBOSITY=quietto reduce trace noise
API reference: Web API.