| title | UAPI.16 OSC 3008a: Hierarchical Context Signalling Append A | ||
|---|---|---|---|
| category | Concepts | ||
| layout | default | ||
| version | 1.0 | ||
| SPDX-License-Identifier | CC-BY-4.0 | ||
| weight | 16 | ||
| aliases |
|
| Version | Changes |
|---|---|
| 1.0 | Initial release |
Note
This specification is an appendix to the main OSC 3008 specification.
It defines the progress sequence for tracking dynamic state and progress within a context, similar to tracing spans.
While the primary OSC 3008 specification provides a mechanism to establish static context boundaries (identifying which component is in control of the terminal), many modern tools require a way to signal dynamic runtime state. Applications such as AI agents, build systems (CMake, Make, Siso, Zig, Cargo), and infrastructure orchestrators (Terraform, Ansible) often execute long-running tasks. Terminal emulators can provide a much richer user experience if they understand the progress and state of these operations.
This appendix defines the progress sequence, which allows a context to emit dynamic updates.
This standardises the concepts pioneered by OSC 7501 spec (implemented by Claude Code, Pi, etc),
bringing them into the open UAPI OSC 3008 family as a hierarchical extension.
Early attempts at context progress signalling (specifically OSC 7501) and the parent OSC 3008 specification revealed several architectural limitations that this specification aims to resolve:
- The Concurrent Sibling Problem (Stack vs Tree): OSC 3008 defines hierarchy using an implicit,
stateful stack ("the previously active context becomes its parent"). While fine for simple linear shells, it breaks down for modern tools like AI agents,
which spawn complex trees of concurrent subagents and subprocesses.
Under OSC 3008, you cannot easily run tasks A, B, and C concurrently as siblings under parent X: returning the active context pointer to X
to spawn B implicitly terminates A. To solve this, applications must use stateless, explicitly segmented identifiers (e.g.
X/A,X/B) so the terminal can reconstruct the tree independently of the stack. - Tree Representation & Record Segmentation: Because concurrent trees require segmented identifiers, a widely adopted format is segmented UUIDs
(e.g.
uuid/uuid). The strict 64-byte ID limit in OSC 7501 made this impossible. OSC 3008a increases the maximum context identifier length to 128 bytes. This allows for at least three layers of nesting with full UUIDs (36 + 1 + 36 + 1 + 36 = 110bytes), which accommodates almost any worst-case scenario (since heavily nested trees should ideally use shorter IDs). - Extensibility (The
kindKey): OSC 7501 restricted state reporting to a rigid set of keys (permission | question | auth). Anything outside that set was treated as absent, rendering the spec inapplicable to the vast majority of sources (like compilers or build systems) that are not AI agents. OSC 3008a resolves this by introducing a universalstateenum and an extensiblereasonfield. - Memory Limits (4096 bytes): OSC 7501 enforces a strict 4096 bytes limit to allow terminal emulators to safely pre-allocate memory. OSC 3008a explicitly acknowledges and inherits this constraint rather than introducing it as a novel limitation, ensuring that terminal emulators can continue to parse these escape sequences safely without unpredictable heap allocations.
To ensure reliable memory allocation in terminal emulators, the following limits are strictly enforced:
- Maximum Sequence Length: The total length of a
progresssequence MUST NOT exceed4096bytes. Terminal emulators MAY allocate this memory on the heap or statically. The spec does not assume where the memory is allocated, but guarantees the upper bound. - Maximum Context ID Length: To cleanly support nested tracing spans and UUID-based record segmentation (e.g.,
uuid/uuid), context identifiers (CTXID) MAY be up to128bytes in length withinprogresssequences. If source emits aCTXIDmore than128bytes, all bytes above128limit MAY be discarded.
The progress sequence is a new verb within the OSC 3008 namespace.
It uses the progress= command to tie dynamic updates to a specific context identifier previously established by a 3008;start= sequence.
The progress sequence is completely orthogonal to the context's type= (e.g. type=app, type=service, type=command);
it MAY be emitted for any active context.
OSC 3008 ; progress= <context_id> [ ; <key> = <value> ... ] STFor example:
OSC "3008;progress=bed86fab93af4328bbed0a1224af6d40;state=running;title=QnVpbGRpbmcgWmln" ST
Important
Unlike standard OSC 3008 fields which use \x3b and \x5c for escaping, textual fields in the progress sequence
that are prone to containing arbitrary user output, Markdown, or ANSI codes MUST be base64-encoded.
This guarantees that complex output will not break the terminal emulator's OSC parser, and reduces implementation drift compared to OSC 7501.
Base64 payloads MUST follow RFC 4648 without line wrapping (i.e., no \r\n or whitespace).
An empty progress sequence (e.g., OSC "3008;progress=uuid" ST with no key-value pairs) is considered a valid no-op. It MUST NOT clear existing state, but terminal emulators MAY use it as a "ping" to update the context's activity timestamp.
The following fields are defined for the progress sequence:
| Field | Description |
|---|---|
state= |
An enumeration representing the current execution state. MUST be one of: idle, running, or blocked. |
reason= |
base64 encoded. An opaque, application-specific state string for the current operation. |
title= |
base64 encoded. A short, human-readable description of the current state. |
msg= |
base64 encoded. A detailed, human-readable description of the current state. |
progress= |
An integer between 0 and 100 representing the percentage of completion. Terminal emulators MUST clamp values > 100 to 100. |
The state field is strictly an enumeration. Terminal emulators MAY use these states to reliably respond to state changes
(e.g. tinting the background, showing a spinner, or rendering a pause icon).
idle: The task is waiting to start, or has finished processing.running: The task is actively processing data or using the CPU.blocked: The task is suspended waiting for user input, network I/O, a child process, etc.
Note
Unlike OSC 7501, which includes done and error states, this specification intentionally omits them. Termination and error reporting are already robustly handled by the parent OSC 3008 end= sequence (e.g., exit=success, exit=failure, exit=crash). Redefining them in the progress sequence would create ambiguity.
If an application wishes to transmit custom state data that does not fit into this triad (e.g. state=linking),
it MUST use the reason= field. Terminal emulators MUST NOT assume the content of the reason field, and should treat it purely as opaque application data.
A typical program or agent will follow this state machine life-cycle:
(start sequence) -> idle -> running -> blocked (waiting for user input) -> running -> idle -> (end sequence)
By explicitly differentiating running and blocked, the terminal emulator (and/or an OSC 3008-compliant sink)
MAY provide immediate visual feedback when an application is hung waiting for a response versus when it is actively processing data.
Note
The optional feature detection mechanism described in this section is heavily inspired by and directly adapted from the robust detection established in OSC 7501.
Detection is optional. Terminals MUST ignore unknown OSC sequences, so a program MAY send progress reports without querying first
(for example, when it cannot read replies from the terminal).
A program queries for support by sending:
OSC "3008;progress=?" ST
Important
Because ? (%x3F) is technically a valid character within the OSC 3008 SAFE character set, the exact context identifier ? is strictly reserved.
Applications MUST NOT use ? as a CTXID. A terminal emulator MUST treat OSC "3008;progress=?" ST exclusively as a feature detection query.
A supporting terminal MUST reply with:
OSC "3008;progress=?;states=idle,running,blocked" ST
A reply without a states= field implies exactly the baseline states: idle, running, and blocked.
A terminal MAY add states to the comma-separated list of states it accepts.
This is how states are added in future revisions: a program that wants a state from a later revision checks the list and falls back to
one that is present if it is not supported. Programs MUST ignore key-value pairs in the reply that they do not understand.
A program that gets no reply within a time of its choosing treats the protocol as unsupported.
To avoid waiting, a program MAY send a Primary Device Attributes query (CSI c) immediately after the feature detection query.
Every terminal answers the Primary Device Attributes query, so if its reply arrives first, the progress protocol is unsupported.
A full lifecycle of a task named "Compile" represented as a span under a parent process:
-
Start the context: The program establishes the context boundary using OSC 3008.
OSC "3008;start=bed86fab93af4328bbed0a1224af6d40;type=app;comm=Compile" ST -
Signal running state: The task begins executing. The
titleis base64-encoded "Compiling Zig".OSC "3008;progress=bed86fab93af4328bbed0a1224af6d40;state=running;title=Q29tcGlsaW5nIFppZw==" ST -
Update progress: The task is 50% complete.
OSC "3008;progress=bed86fab93af4328bbed0a1224af6d40;state=running;progress=50" ST -
Signal blocked state: The task suspends to wait for network I/O. The
reasonis base64-encoded "network".OSC "3008;progress=bed86fab93af4328bbed0a1224af6d40;state=blocked;reason=bmV0d29yaw==" ST -
End the context: The task completes successfully. Termination is handled by the parent OSC 3008.
OSC "3008;end=bed86fab93af4328bbed0a1224af6d40;exit=success" ST
; Ref: https://en.wikipedia.org/wiki/Augmented_Backus–Naur_form
; UAPI.15 (OSC3008)
OSC = %x1B %x5D
ST = %x1B %x5C
DECIMAL = "0"-"9"
ESCSEMICOLON = "\x3b"
ESCBACKSLASH = "\x5c"
SAFE = %x20-3a / %x3c-5b / %x5d-7e / ESCSEMICOLON / ESCBACKSLASH
CTXID = 1*128SAFE
; UAPI.16 (OSC3008a)
B64CHAR = "a"-"z" / "A"-"Z" / "0"-"9" / "+" / "/" / "="
STATEENUM = "idle" / "running" / "blocked"
PSTATE = "state=" STATEENUM
PREASON = "reason=" *B64CHAR
PTITLE = "title=" *B64CHAR
PMSG = "msg=" *B64CHAR
PPROGRESS = "progress=" 1*3DECIMAL
PROGRESSFIELD = PSTATE / PREASON / PTITLE / PMSG / PPROGRESS
; The total length of PROGRESSSEQ MUST NOT exceed 4096 bytes.
; Terminal emulators MUST truncate or reject sequences exceeding this limit.
PROGRESSSEQ = OSC "3008;progress=" CTXID *(";" PROGRESSFIELD) ST