# Program status protocol (OSC 7501)

Programs running in Solo can report what they're doing with OSC 7501, the program status protocol: working, blocked on you, done, failed, or idle. Solo uses those reports to drive agent activity indicators and attention alerts, and to hold automated messages while a program waits for you.

This is useful when you build a CLI agent, a long-running script, or a TUI and want Solo to know its real state instead of guessing from output.

## Send a report

A report is an OSC sequence with `key=value` pairs separated by colons:

```text
ESC ] 7501 ; key=value:key=value BEL
```

You can end the sequence with `BEL` (`\007`) or `ESC \`. The whole sequence, from `ESC ]` to the terminator, can be at most 4,096 bytes.

For example, report that a build finished:

```bash
printf '\033]7501;state=done:msg=%s\007' "$(printf 'Build finished' | base64)"
```

Report that the program is working, then that it's waiting for permission:

```bash
printf '\033]7501;state=working:progress=40\007'
printf '\033]7501;state=blocked:kind=permission:msg=%s\007' "$(printf 'Allow deploy?' | base64)"
```

## Keys

| Key | Value |
|-----|-------|
| `state` | Required. `idle`, `working`, `blocked`, `done`, `error`, or `clear`. |
| `kind` | Only with `blocked`: `permission`, `question`, or `auth`. |
| `progress` | Only with `working` or `blocked`: a whole number from 0 to 100. |
| `id` | Optional record ID, such as `build` or `build/tests`. Up to 8 `/`-separated segments of letters, digits, `_`, `.`, `+`, or `-`; at most 32 bytes per segment and 128 bytes in total. |
| `app` | Optional program name, up to 32 bytes. |
| `title` | Optional notification title, **base64-encoded** UTF-8, up to 192 bytes once decoded. |
| `msg` | Optional notification message, **base64-encoded** UTF-8, up to 2,048 bytes once decoded. |

Keys are lowercase. Base64 padding is optional. Solo rejects a report that is missing `state`, uses an unknown state, or breaks a length limit; it ignores keys it doesn't recognize.

Failure is reported as `state=error`.

## Records and clearing

Each report replaces the record for its `id`. A report without `id` sets the program's root record, so a simple program never needs one. Programs that track several tasks can give each its own ID.

`state=clear` removes records instead of setting one:

- With an `id`, it clears that record and everything under it. `state=clear:id=build` clears `build` and `build/tests`, but not `builder`.
- Without an `id`, it clears every record for the terminal.

## Check for support

To find out whether the terminal supports the protocol, send the feature query:

```text
ESC ] 7501 ; ? BEL
```

Solo answers with the same sequence, using the terminator your query used. This reply is the only data the protocol ever writes back to the program.

## How Solo uses reports

### Activity

For agents and terminals, `working` and `blocked` reports take precedence over Solo's own [activity heuristics](../agents/idle-detection.md). A program that reports `working` stays busy through quiet periods, and a `blocked` program isn't shown as working even while it redraws the screen. When no working or blocked record remains, Solo goes back to its usual detection.

### Attention

A record that newly becomes `blocked`, `done`, or `error`, or whose blocked `kind` changes, marks the process as needing attention and can send a [notification](../notifications/triggering-from-scripts.md). Repeating the same report or updating `progress` doesn't alert you again.

The notification title is the record's `title`, or `app` when there's no title. The message is `msg`, or a default based on the state:

| State | Default message |
|-------|-----------------|
| `blocked` with `kind=permission` | Waiting for permission |
| `blocked` with `kind=question` | Waiting for an answer |
| `blocked` with `kind=auth` | Waiting for sign-in |
| `blocked` without `kind` | Waiting for input |
| `error` | Failed |
| `done` | Finished |

When the record leaves that state, for example the program reports `working` again, Solo removes the attention it raised. Other unread notifications for the process stay.

Program status alerts follow the same rule as BEL and other OSC alerts: they only appear when the effective [notification level](../notifications/indicators.md) for the project and process is **All**.

### Automated messages

While a program reports `blocked`, Solo holds [timer](../mcp-tools/timers.md) and MCP messages for that process and delivers them after it continues. For idle-triggered timers, a blocked process counts as idle, because it's waiting on you.

## When records expire

- `working`, `blocked`, and `idle` records end when the program's process exits, when a new shell prompt starts, and when the terminal is reset.
- `done` and `error` records survive the process exiting, so a finished or failed result stays visible.
- Starting a new run of the process starts with no records.

## Related pages

- [OSC, links, and Kitty keyboard protocol](./osc-kitty-protocol.md)
- [Triggering notifications from scripts](../notifications/triggering-from-scripts.md)
- [Agent idle detection](../agents/idle-detection.md)

---

Are you a human? Read this doc on the web: https://soloterm.com/docs/terminal/program-status
