{
  "@context": "https://schema.org",
  "@id": "https://aggr.aymericbeaumet.com/items/mitchellh-com/2026-10-06-a-terminal-protocol-for-program-status-osc-7501/#webpage",
  "@type": [
    "WebPage",
    "ArchiveComponent"
  ],
  "articleBody": "I wrote a specification for a new terminal escape sequence: [OSC 7501, the Program Status Protocol](https://www.superlogical.com/rex/docs/build/program-status). It lets any program tell the terminal what it's doing: idle, working, waiting on the user, finished, or failed, and why.\n\nFor example, here is how [Terraform](https://developer.hashicorp.com/terraform) could indicate that it is blocked waiting for user input, with the message \"Apply 3 to add, 1 to change, 0 to destroy?\" (base64-encoded). A terminal (or any other tool running Terraform) could show this information however it feels appropriate: a notification, an inbox, a status icon, etc.\n\n```\nESC ] 7501 ; state=blocked:kind=permission:app=terraform:msg=QXBwbHkgMyB0byBhZGQsIDEgdG8gY2hhbmdlLCAwIHRvIGRlc3Ryb3k/ ESC \\\n```\n\nThis post covers why I think this protocol needs to exist, why the existing approaches aren't good enough (especially for coding agents), and how the protocol works.\n\n**This is a completely generic, terminal-native specification and protocol.** It emerged from my work on [Superlogical](https://mitchellh.com/writing/superlogical) and [Ghostty](https://ghostty.org/) but [the specification](https://www.superlogical.com/rex/docs/build/program-status) has no product-specific functionality or language. It is designed as an idiomatic, well-formed specification that any terminal developer will find familiar.\n\n* * *\n\n## The Problem\n\nLong-running work is common in terminals: builds, deployments, package upgrades, data processing, and, more and more today, coding agents. These programs alternate between working on their own, waiting on the user, and finishing. Meanwhile, users usually go off and do something else and want to know when the work finishes or needs them.\n\nAspects of this problem have been solved in various ways going back decades. For example, some terminals monitor the active foreground process and have features to notify when it changes. Or, they wait for some time period of \"quiet\" (for various definitions) output. The specification also [lists the reasons why existing sequences aren't enough](https://www.superlogical.com/rex/docs/build/program-status#relationship-to-other-sequences).\n\nUltimately, I felt there wasn't a cohesive, interaction-agnostic, generic solution to this problem that conveyed progress, blocking, completion, and trees of tasks. And it wasn't possible to cobble together pre-existing sequences to achieve it robustly, either.\n\n* * *\n\n## Singling Out \"Agentic Inboxes\"\n\n**Don't care about AI, LLMs, etc.? Skip this section.** The problem is generic and applies in a compelling way without bringing in AI. It's particularly nasty with AI so I want to call it out, but if you don't care about any of that, just skip this.\n\nIt's now increasingly common for people to run many long-running agents for any number of reasons: background research, issue monitoring, bug fixing, large features, etc. Each one works for a while and then stops to ask for permission, ask a question, or report it's done.\n\nFrom this, a new category of tool has emerged that I'll simply call the *agentic inbox*: a single view across every running agent showing which are working, which are done, and which are waiting on you. [Herdr](https://herdr.dev/), [cmux](https://cmux.com/), and [Agent Deck](https://github.com/asheshgoplani/agent-deck) are a few examples of [hundreds](https://github.com/andyrewlee/awesome-agent-orchestrators).\n\nWithout a dedicated protocol, they solve the agent status problem in two ways: heuristics and non-terminal APIs.\n\n### Heuristics\n\nThe first approach is to guess by reading the screen or the window title and matching it against known patterns.\n\nHerdr is a good example because it does this well and documents it openly. Its [detection manifests](https://herdr.dev/docs/agents/#detection-manifests) are TOML rules that classify an agent as idle, working, or blocked. Here is [the first of 16 rules](https://github.com/herdrdev/herdr/blob/3d9d2b18dab139ba226ebc5a1c9a9f2c9c3ee4df/src/detect/manifests/claude.toml#L7-L14) for Claude Code:\n\n```\n[[rules]]\nid = \"osc_title_working\"\nstate = \"working\"\npriority = 1100\nregion = \"osc_title\"\nvisible_working = true\n# Braille covers <= 2.1.227; half-circles are the 2.1.228 busy spinner.\nregex = ['^[\\x{2800}-\\x{28FF}\\x{25D0}-\\x{25D3}] ']\n```\n\nClaude Code is considered \"working\" if its window title starts with a Braille spinner character or, as of version 2.1.228, a half-circle one. The [history of that file](https://github.com/herdrdev/herdr/commits/master/src/detect/manifests/claude.toml) shows ten changes in three months for just Claude Code.\n\n**This isn't a criticism of Herdr.** Its maintainers are doing the best possible job with the tools available. But it demonstrates well the benefit [a unified protocol](https://www.superlogical.com/rex/docs/build/program-status) would have.\n\n### Non-Terminal APIs\n\nThe second approach is to have the program report its own state through an inbox-specific, out-of-band API such as [Herdr's socket API](https://herdr.dev/docs/socket-api/#agent-state-reporting) or [`cmux notify`](https://cmux.com/docs/notifications). This is better than heuristics in some ways, because the program that actually knows its state is the one reporting it. But every program has to integrate with every inbox separately, and a local socket doesn't work over SSH or from within a container without extra bridging. The pty already works across all of this.\n\n* * *\n\n## The Program Status Protocol\n\n[OSC 7501](https://www.superlogical.com/rex/docs/build/program-status) is a terminal native answer to this problem. The program reports its own state directly via the pty that it always has, using a format that is safe to send everywhere (well-behaved terminals ignore unknown OSCs).\n\nThe body of the sequence is a [list of `key=value` pairs](https://www.superlogical.com/rex/docs/build/program-status#syntax) separated by `:`. The only required key is [`state`](https://www.superlogical.com/rex/docs/build/program-status#states), which is one of:\n\n| `state`   | Meaning                                                                                                                  |\n| --------- | ------------------------------------------------------------------------------------------------------------------------ |\n| `idle`    | At rest, waiting for the user's next instruction.                                                                        |\n| `working` | Running. May include a `progress` percentage.                                                                            |\n| `done`    | Finished. The result is ready and the user hasn't seen it yet.                                                           |\n| `blocked` | Can't continue until the user does something. `kind` says what (`permission`, `question`, or `auth`) and `msg` says why. |\n| `error`   | Failed and stopped.                                                                                                      |\n\n[Optional keys](https://www.superlogical.com/rex/docs/build/program-status#keys) include `app`, a stable machine-readable program name like `cargo` or `claude-code`, and `msg`, a single human-readable line encoded as base64.\n\nPrograms that run several things at once can report multiple records using [hierarchical ids](https://www.superlogical.com/rex/docs/build/program-status#records-and-ids). A [deploy tool](https://www.superlogical.com/rex/docs/build/program-status#several-records) can be `working` at the root while `us-east` pushes an image at 40% and `eu-west` is `blocked` waiting for approval to deploy to production. Both are true at the same time, and the terminal decides what to show. A `clear` state removes records.\n\nHere's a [complete integration](https://www.superlogical.com/rex/docs/build/program-status#a-shell-function) for a shell script to wrap `rsync` to participate in this protocol:\n\n```\nstatus() {\n  printf '\\e]7501;state=%s:msg=%s\\e\\\\' \"$1\" \"$(printf '%s' \"$2\" | base64 | tr -d '\\n')\"\n}\nstatus working \"Syncing photos\"\nrsync -a ~/Photos backup:/photos && status done \"Photos synced\" || status error \"rsync failed\"\n```\n\nTrivial to script with plain old POSIX `sh`. No SDK, no sockets, no environment variables, no JSON. No bias towards specific GUI presentations. No bias to any specific workload (like AI). A well-formed, generic foundation to build functionality above that anyone and everyone can participate with.\n\nThe [full spec](https://www.superlogical.com/rex/docs/build/program-status) covers the rest: [record lifetime](https://www.superlogical.com/rex/docs/build/program-status#states), [feature detection](https://www.superlogical.com/rex/docs/build/program-status#feature-detection), [terminfo](https://www.superlogical.com/rex/docs/build/program-status#terminfo), [size limits](https://www.superlogical.com/rex/docs/build/program-status#limits), and [security](https://www.superlogical.com/rex/docs/build/program-status#security). It's short. I wrote it all by hand. Please read it.\n\n* * *\n\n## Implement It\n\nI wrote [this specification](https://www.superlogical.com/rex/docs/build/program-status) based on my experience maintaining a terminal emulator for many years now. It is written in a way that is easy for application developers to emit, and easy for terminal emulators to consume and parse.\n\nI've already implemented this protocol twice. We have one implementation in [libghostty](https://github.com/ghostty-org/ghostty/pull/14560) and I have a parallel implementation in [Rex](https://www.superlogical.com/rex). I've also implemented it as a proof-of-concept in Terraform, Claude Code, Codex, and Homebrew via either plugins or forks. In each case, the implementation was no more than a dozen lines.\n\nI've been in contact with the maintainers of many popular terminal programs and emulators and they've helped review and shape the specification. But if you have any more feedback, I'm happy to hear it.\n\n**If you've implemented the specification** please let me know via email (mail icon in the footer) and I'll add you to the list of tools that implement this. Thank you.\n\nI'd like us all to stop guessing what programs are doing by reading their screens or process trees. The program already knows. Let's give it a way to tell us!\n",
  "authors": [],
  "captured_at": "2026-10-06T23:45:23.660353536+00:00",
  "category": "programming",
  "content_html": "<p>I wrote a specification for a new terminal escape sequence: <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.superlogical.com/rex/docs/build/program-status\">OSC 7501, the Program Status Protocol</a>. It lets any program tell the terminal what it's doing: idle, working, waiting on the user, finished, or failed, and why.</p>\n<p>For example, here is how <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://developer.hashicorp.com/terraform\">Terraform</a> could indicate that it is blocked waiting for user input, with the message &quot;Apply 3 to add, 1 to change, 0 to destroy?&quot; (base64-encoded). A terminal (or any other tool running Terraform) could show this information however it feels appropriate: a notification, an inbox, a status icon, etc.</p>\n<pre><code><span class=\"code-snippet\">ESC ] 7501 ; state=blocked:kind=permission:app=terraform:msg=QXBwbHkgMyB0byBhZGQsIDEgdG8gY2hhbmdlLCAwIHRvIGRlc3Ryb3k/ ESC \\\n</span></code></pre>\n<p>This post covers why I think this protocol needs to exist, why the existing approaches aren't good enough (especially for coding agents), and how the protocol works.</p>\n<p><strong>This is a completely generic, terminal-native specification and protocol.</strong> It emerged from my work on <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://mitchellh.com/writing/superlogical\">Superlogical</a> and <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://ghostty.org/\">Ghostty</a> but <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.superlogical.com/rex/docs/build/program-status\">the specification</a> has no product-specific functionality or language. It is designed as an idiomatic, well-formed specification that any terminal developer will find familiar.</p>\n<hr />\n<h2>The Problem</h2>\n<p>Long-running work is common in terminals: builds, deployments, package upgrades, data processing, and, more and more today, coding agents. These programs alternate between working on their own, waiting on the user, and finishing. Meanwhile, users usually go off and do something else and want to know when the work finishes or needs them.</p>\n<p>Aspects of this problem have been solved in various ways going back decades. For example, some terminals monitor the active foreground process and have features to notify when it changes. Or, they wait for some time period of &quot;quiet&quot; (for various definitions) output. The specification also <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.superlogical.com/rex/docs/build/program-status#relationship-to-other-sequences\">lists the reasons why existing sequences aren't enough</a>.</p>\n<p>Ultimately, I felt there wasn't a cohesive, interaction-agnostic, generic solution to this problem that conveyed progress, blocking, completion, and trees of tasks. And it wasn't possible to cobble together pre-existing sequences to achieve it robustly, either.</p>\n<hr />\n<h2>Singling Out &quot;Agentic Inboxes&quot;</h2>\n<p><strong>Don't care about AI, LLMs, etc.? Skip this section.</strong> The problem is generic and applies in a compelling way without bringing in AI. It's particularly nasty with AI so I want to call it out, but if you don't care about any of that, just skip this.</p>\n<p>It's now increasingly common for people to run many long-running agents for any number of reasons: background research, issue monitoring, bug fixing, large features, etc. Each one works for a while and then stops to ask for permission, ask a question, or report it's done.</p>\n<p>From this, a new category of tool has emerged that I'll simply call the <em>agentic inbox</em>: a single view across every running agent showing which are working, which are done, and which are waiting on you. <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://herdr.dev/\">Herdr</a>, <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://cmux.com/\">cmux</a>, and <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/asheshgoplani/agent-deck\">Agent Deck</a> are a few examples of <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/andyrewlee/awesome-agent-orchestrators\">hundreds</a>.</p>\n<p>Without a dedicated protocol, they solve the agent status problem in two ways: heuristics and non-terminal APIs.</p>\n<h3>Heuristics</h3>\n<p>The first approach is to guess by reading the screen or the window title and matching it against known patterns.</p>\n<p>Herdr is a good example because it does this well and documents it openly. Its <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://herdr.dev/docs/agents/#detection-manifests\">detection manifests</a> are TOML rules that classify an agent as idle, working, or blocked. Here is <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/herdrdev/herdr/blob/3d9d2b18dab139ba226ebc5a1c9a9f2c9c3ee4df/src/detect/manifests/claude.toml#L7-L14\">the first of 16 rules</a> for Claude Code:</p>\n<pre><code><span class=\"code-snippet\" data-language=\"TOML\">[[rules]]\nid = &quot;osc_title_working&quot;\nstate = &quot;working&quot;\npriority = 1100\nregion = &quot;osc_title&quot;\nvisible_working = true\n# Braille covers &lt;= 2.1.227; half-circles are the 2.1.228 busy spinner.\nregex = [&#39;^[\\x{2800}-\\x{28FF}\\x{25D0}-\\x{25D3}] &#39;]\n</span></code></pre>\n<p>Claude Code is considered &quot;working&quot; if its window title starts with a Braille spinner character or, as of version 2.1.228, a half-circle one. The <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/herdrdev/herdr/commits/master/src/detect/manifests/claude.toml\">history of that file</a> shows ten changes in three months for just Claude Code.</p>\n<p><strong>This isn't a criticism of Herdr.</strong> Its maintainers are doing the best possible job with the tools available. But it demonstrates well the benefit <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.superlogical.com/rex/docs/build/program-status\">a unified protocol</a> would have.</p>\n<h3>Non-Terminal APIs</h3>\n<p>The second approach is to have the program report its own state through an inbox-specific, out-of-band API such as <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://herdr.dev/docs/socket-api/#agent-state-reporting\">Herdr's socket API</a> or <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://cmux.com/docs/notifications\"><code>cmux notify</code></a>. This is better than heuristics in some ways, because the program that actually knows its state is the one reporting it. But every program has to integrate with every inbox separately, and a local socket doesn't work over SSH or from within a container without extra bridging. The pty already works across all of this.</p>\n<hr />\n<h2>The Program Status Protocol</h2>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.superlogical.com/rex/docs/build/program-status\">OSC 7501</a> is a terminal native answer to this problem. The program reports its own state directly via the pty that it always has, using a format that is safe to send everywhere (well-behaved terminals ignore unknown OSCs).</p>\n<p>The body of the sequence is a <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.superlogical.com/rex/docs/build/program-status#syntax\">list of <code>key=value</code> pairs</a> separated by <code>:</code>. The only required key is <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.superlogical.com/rex/docs/build/program-status#states\"><code>state</code></a>, which is one of:</p>\n<table>\n<thead>\n<tr>\n<th><code>state</code></th>\n<th>Meaning</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>idle</code></td>\n<td>At rest, waiting for the user's next instruction.</td>\n</tr>\n<tr>\n<td><code>working</code></td>\n<td>Running. May include a <code>progress</code> percentage.</td>\n</tr>\n<tr>\n<td><code>done</code></td>\n<td>Finished. The result is ready and the user hasn't seen it yet.</td>\n</tr>\n<tr>\n<td><code>blocked</code></td>\n<td>Can't continue until the user does something. <code>kind</code> says what (<code>permission</code>, <code>question</code>, or <code>auth</code>) and <code>msg</code> says why.</td>\n</tr>\n<tr>\n<td><code>error</code></td>\n<td>Failed and stopped.</td>\n</tr>\n</tbody>\n</table>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.superlogical.com/rex/docs/build/program-status#keys\">Optional keys</a> include <code>app</code>, a stable machine-readable program name like <code>cargo</code> or <code>claude-code</code>, and <code>msg</code>, a single human-readable line encoded as base64.</p>\n<p>Programs that run several things at once can report multiple records using <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.superlogical.com/rex/docs/build/program-status#records-and-ids\">hierarchical ids</a>. A <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.superlogical.com/rex/docs/build/program-status#several-records\">deploy tool</a> can be <code>working</code> at the root while <code>us-east</code> pushes an image at 40% and <code>eu-west</code> is <code>blocked</code> waiting for approval to deploy to production. Both are true at the same time, and the terminal decides what to show. A <code>clear</code> state removes records.</p>\n<p>Here's a <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.superlogical.com/rex/docs/build/program-status#a-shell-function\">complete integration</a> for a shell script to wrap <code>rsync</code> to participate in this protocol:</p>\n<pre><code><span class=\"code-snippet\">status() {\n  printf &#39;\\e]7501;state=%s:msg=%s\\e\\\\&#39; &quot;$1&quot; &quot;$(printf &#39;%s&#39; &quot;$2&quot; | base64 | tr -d &#39;\\n&#39;)&quot;\n}\nstatus working &quot;Syncing photos&quot;\nrsync -a ~/Photos backup:/photos &amp;&amp; status done &quot;Photos synced&quot; || status error &quot;rsync failed&quot;\n</span></code></pre>\n<p>Trivial to script with plain old POSIX <code>sh</code>. No SDK, no sockets, no environment variables, no JSON. No bias towards specific GUI presentations. No bias to any specific workload (like AI). A well-formed, generic foundation to build functionality above that anyone and everyone can participate with.</p>\n<p>The <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.superlogical.com/rex/docs/build/program-status\">full spec</a> covers the rest: <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.superlogical.com/rex/docs/build/program-status#states\">record lifetime</a>, <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.superlogical.com/rex/docs/build/program-status#feature-detection\">feature detection</a>, <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.superlogical.com/rex/docs/build/program-status#terminfo\">terminfo</a>, <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.superlogical.com/rex/docs/build/program-status#limits\">size limits</a>, and <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.superlogical.com/rex/docs/build/program-status#security\">security</a>. It's short. I wrote it all by hand. Please read it.</p>\n<hr />\n<h2>Implement It</h2>\n<p>I wrote <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.superlogical.com/rex/docs/build/program-status\">this specification</a> based on my experience maintaining a terminal emulator for many years now. It is written in a way that is easy for application developers to emit, and easy for terminal emulators to consume and parse.</p>\n<p>I've already implemented this protocol twice. We have one implementation in <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/ghostty-org/ghostty/pull/14560\">libghostty</a> and I have a parallel implementation in <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://www.superlogical.com/rex\">Rex</a>. I've also implemented it as a proof-of-concept in Terraform, Claude Code, Codex, and Homebrew via either plugins or forks. In each case, the implementation was no more than a dozen lines.</p>\n<p>I've been in contact with the maintainers of many popular terminal programs and emulators and they've helped review and shape the specification. But if you have any more feedback, I'm happy to hear it.</p>\n<p><strong>If you've implemented the specification</strong> please let me know via email (mail icon in the footer) and I'll add you to the list of tools that implement this. Thank you.</p>\n<p>I'd like us all to stop guessing what programs are doing by reading their screens or process trees. The program already knows. Let's give it a way to tell us!</p>\n",
  "content_markdown": "I wrote a specification for a new terminal escape sequence: [OSC 7501, the Program Status Protocol](https://www.superlogical.com/rex/docs/build/program-status). It lets any program tell the terminal what it's doing: idle, working, waiting on the user, finished, or failed, and why.\n\nFor example, here is how [Terraform](https://developer.hashicorp.com/terraform) could indicate that it is blocked waiting for user input, with the message \"Apply 3 to add, 1 to change, 0 to destroy?\" (base64-encoded). A terminal (or any other tool running Terraform) could show this information however it feels appropriate: a notification, an inbox, a status icon, etc.\n\n```\nESC ] 7501 ; state=blocked:kind=permission:app=terraform:msg=QXBwbHkgMyB0byBhZGQsIDEgdG8gY2hhbmdlLCAwIHRvIGRlc3Ryb3k/ ESC \\\n```\n\nThis post covers why I think this protocol needs to exist, why the existing approaches aren't good enough (especially for coding agents), and how the protocol works.\n\n**This is a completely generic, terminal-native specification and protocol.** It emerged from my work on [Superlogical](https://mitchellh.com/writing/superlogical) and [Ghostty](https://ghostty.org/) but [the specification](https://www.superlogical.com/rex/docs/build/program-status) has no product-specific functionality or language. It is designed as an idiomatic, well-formed specification that any terminal developer will find familiar.\n\n* * *\n\n## The Problem\n\nLong-running work is common in terminals: builds, deployments, package upgrades, data processing, and, more and more today, coding agents. These programs alternate between working on their own, waiting on the user, and finishing. Meanwhile, users usually go off and do something else and want to know when the work finishes or needs them.\n\nAspects of this problem have been solved in various ways going back decades. For example, some terminals monitor the active foreground process and have features to notify when it changes. Or, they wait for some time period of \"quiet\" (for various definitions) output. The specification also [lists the reasons why existing sequences aren't enough](https://www.superlogical.com/rex/docs/build/program-status#relationship-to-other-sequences).\n\nUltimately, I felt there wasn't a cohesive, interaction-agnostic, generic solution to this problem that conveyed progress, blocking, completion, and trees of tasks. And it wasn't possible to cobble together pre-existing sequences to achieve it robustly, either.\n\n* * *\n\n## Singling Out \"Agentic Inboxes\"\n\n**Don't care about AI, LLMs, etc.? Skip this section.** The problem is generic and applies in a compelling way without bringing in AI. It's particularly nasty with AI so I want to call it out, but if you don't care about any of that, just skip this.\n\nIt's now increasingly common for people to run many long-running agents for any number of reasons: background research, issue monitoring, bug fixing, large features, etc. Each one works for a while and then stops to ask for permission, ask a question, or report it's done.\n\nFrom this, a new category of tool has emerged that I'll simply call the *agentic inbox*: a single view across every running agent showing which are working, which are done, and which are waiting on you. [Herdr](https://herdr.dev/), [cmux](https://cmux.com/), and [Agent Deck](https://github.com/asheshgoplani/agent-deck) are a few examples of [hundreds](https://github.com/andyrewlee/awesome-agent-orchestrators).\n\nWithout a dedicated protocol, they solve the agent status problem in two ways: heuristics and non-terminal APIs.\n\n### Heuristics\n\nThe first approach is to guess by reading the screen or the window title and matching it against known patterns.\n\nHerdr is a good example because it does this well and documents it openly. Its [detection manifests](https://herdr.dev/docs/agents/#detection-manifests) are TOML rules that classify an agent as idle, working, or blocked. Here is [the first of 16 rules](https://github.com/herdrdev/herdr/blob/3d9d2b18dab139ba226ebc5a1c9a9f2c9c3ee4df/src/detect/manifests/claude.toml#L7-L14) for Claude Code:\n\n```\n[[rules]]\nid = \"osc_title_working\"\nstate = \"working\"\npriority = 1100\nregion = \"osc_title\"\nvisible_working = true\n# Braille covers <= 2.1.227; half-circles are the 2.1.228 busy spinner.\nregex = ['^[\\x{2800}-\\x{28FF}\\x{25D0}-\\x{25D3}] ']\n```\n\nClaude Code is considered \"working\" if its window title starts with a Braille spinner character or, as of version 2.1.228, a half-circle one. The [history of that file](https://github.com/herdrdev/herdr/commits/master/src/detect/manifests/claude.toml) shows ten changes in three months for just Claude Code.\n\n**This isn't a criticism of Herdr.** Its maintainers are doing the best possible job with the tools available. But it demonstrates well the benefit [a unified protocol](https://www.superlogical.com/rex/docs/build/program-status) would have.\n\n### Non-Terminal APIs\n\nThe second approach is to have the program report its own state through an inbox-specific, out-of-band API such as [Herdr's socket API](https://herdr.dev/docs/socket-api/#agent-state-reporting) or [`cmux notify`](https://cmux.com/docs/notifications). This is better than heuristics in some ways, because the program that actually knows its state is the one reporting it. But every program has to integrate with every inbox separately, and a local socket doesn't work over SSH or from within a container without extra bridging. The pty already works across all of this.\n\n* * *\n\n## The Program Status Protocol\n\n[OSC 7501](https://www.superlogical.com/rex/docs/build/program-status) is a terminal native answer to this problem. The program reports its own state directly via the pty that it always has, using a format that is safe to send everywhere (well-behaved terminals ignore unknown OSCs).\n\nThe body of the sequence is a [list of `key=value` pairs](https://www.superlogical.com/rex/docs/build/program-status#syntax) separated by `:`. The only required key is [`state`](https://www.superlogical.com/rex/docs/build/program-status#states), which is one of:\n\n| `state`   | Meaning                                                                                                                  |\n| --------- | ------------------------------------------------------------------------------------------------------------------------ |\n| `idle`    | At rest, waiting for the user's next instruction.                                                                        |\n| `working` | Running. May include a `progress` percentage.                                                                            |\n| `done`    | Finished. The result is ready and the user hasn't seen it yet.                                                           |\n| `blocked` | Can't continue until the user does something. `kind` says what (`permission`, `question`, or `auth`) and `msg` says why. |\n| `error`   | Failed and stopped.                                                                                                      |\n\n[Optional keys](https://www.superlogical.com/rex/docs/build/program-status#keys) include `app`, a stable machine-readable program name like `cargo` or `claude-code`, and `msg`, a single human-readable line encoded as base64.\n\nPrograms that run several things at once can report multiple records using [hierarchical ids](https://www.superlogical.com/rex/docs/build/program-status#records-and-ids). A [deploy tool](https://www.superlogical.com/rex/docs/build/program-status#several-records) can be `working` at the root while `us-east` pushes an image at 40% and `eu-west` is `blocked` waiting for approval to deploy to production. Both are true at the same time, and the terminal decides what to show. A `clear` state removes records.\n\nHere's a [complete integration](https://www.superlogical.com/rex/docs/build/program-status#a-shell-function) for a shell script to wrap `rsync` to participate in this protocol:\n\n```\nstatus() {\n  printf '\\e]7501;state=%s:msg=%s\\e\\\\' \"$1\" \"$(printf '%s' \"$2\" | base64 | tr -d '\\n')\"\n}\nstatus working \"Syncing photos\"\nrsync -a ~/Photos backup:/photos && status done \"Photos synced\" || status error \"rsync failed\"\n```\n\nTrivial to script with plain old POSIX `sh`. No SDK, no sockets, no environment variables, no JSON. No bias towards specific GUI presentations. No bias to any specific workload (like AI). A well-formed, generic foundation to build functionality above that anyone and everyone can participate with.\n\nThe [full spec](https://www.superlogical.com/rex/docs/build/program-status) covers the rest: [record lifetime](https://www.superlogical.com/rex/docs/build/program-status#states), [feature detection](https://www.superlogical.com/rex/docs/build/program-status#feature-detection), [terminfo](https://www.superlogical.com/rex/docs/build/program-status#terminfo), [size limits](https://www.superlogical.com/rex/docs/build/program-status#limits), and [security](https://www.superlogical.com/rex/docs/build/program-status#security). It's short. I wrote it all by hand. Please read it.\n\n* * *\n\n## Implement It\n\nI wrote [this specification](https://www.superlogical.com/rex/docs/build/program-status) based on my experience maintaining a terminal emulator for many years now. It is written in a way that is easy for application developers to emit, and easy for terminal emulators to consume and parse.\n\nI've already implemented this protocol twice. We have one implementation in [libghostty](https://github.com/ghostty-org/ghostty/pull/14560) and I have a parallel implementation in [Rex](https://www.superlogical.com/rex). I've also implemented it as a proof-of-concept in Terraform, Claude Code, Codex, and Homebrew via either plugins or forks. In each case, the implementation was no more than a dozen lines.\n\nI've been in contact with the maintainers of many popular terminal programs and emulators and they've helped review and shape the specification. But if you have any more feedback, I'm happy to hear it.\n\n**If you've implemented the specification** please let me know via email (mail icon in the footer) and I'll add you to the list of tools that implement this. Thank you.\n\nI'd like us all to stop guessing what programs are doing by reading their screens or process trees. The program already knows. Let's give it a way to tell us!\n",
  "dateCreated": "2026-10-06T23:45:23.660353536+00:00",
  "date_created": "2026-10-06T00:00:00+00:00",
  "date_modified": "2026-10-06T00:00:00+00:00",
  "date_published": "2026-10-06T00:00:00+00:00",
  "description": "I wrote a specification for a new terminal escape sequence: OSC 7501, the Program Status Protocol. It lets any program tell the terminal what it's doing: idle, working, waiting on the user, finished, or failed, and why. For example, here…",
  "external_url": "https://mitchellh.com/writing/program-status-osc7501",
  "git": {
    "commit": "ebb0941c5c06a2ab2377342d607535262a3e969e",
    "path": "items/mitchellh-com/2026/10/2026-10-06-a-terminal-protocol-for-program-status-osc-7501.md",
    "permalink": "https://github.com/aymericbeaumet/aggr-instance/blob/ebb0941c5c06a2ab2377342d607535262a3e969e/items/mitchellh-com/2026/10/2026-10-06-a-terminal-protocol-for-program-status-osc-7501.md"
  },
  "id": "urn:aggr:item:0a775d92bd5862225a128d760242c3a960021362",
  "isBasedOn": {
    "@id": "https://mitchellh.com/writing/program-status-osc7501"
  },
  "mainEntity": {
    "@id": "https://mitchellh.com/writing/program-status-osc7501",
    "@type": "CreativeWork",
    "archivedAt": {
      "@id": "https://aggr.aymericbeaumet.com/items/mitchellh-com/2026-10-06-a-terminal-protocol-for-program-status-osc-7501/#webpage"
    },
    "dateModified": "2026-10-06T00:00:00+00:00",
    "datePublished": "2026-10-06T00:00:00+00:00",
    "headline": "A Terminal Protocol for Program Status (OSC 7501)",
    "timeRequired": "PT6M",
    "url": "https://mitchellh.com/writing/program-status-osc7501",
    "wordCount": 1244
  },
  "name": "Archived snapshot: A Terminal Protocol for Program Status (OSC 7501)",
  "preview": null,
  "reading_minutes": 6,
  "replicated_at": null,
  "source": "mitchellh-com",
  "source_name": "Mitchell Hashimoto",
  "summary": null,
  "tags": [],
  "temporalCoverage": "2026-10-06T23:45:23.660353536+00:00",
  "title": "A Terminal Protocol for Program Status (OSC 7501)",
  "url": "https://aggr.aymericbeaumet.com/items/mitchellh-com/2026-10-06-a-terminal-protocol-for-program-status-osc-7501/",
  "word_count": 1244
}