Cloud actions say what they do: action describe and MCP tools carry a description

TE
11d ago
+397-2

Audit group J (cloud ergonomics, p0-machines): action descriptions for cmux action describe and the MCP tools.

Leo: "so chieff related stuff we dont need to worry too much about but ergo of commands and docs and error/hint messages we do need ... anyways those are just quite a few things that we already kinda own/should prob own"

Lawrence: "start new VMs from acp chat"

The problem

An agent saw only an action's title. cmux action describe <id> showed title, arguments and targets, and every MCP action tool's description was the same title-only boilerplate ("Move to Cloud. cmux app action cloudMove ..."). Nothing said what an action does, what it needs, what it returns, or that an agent's cloud move only asks the person.

The change

  • ActionDescriptor gets public var description: String? (init parameter description: String? = nil, right after title:). English and agent-facing, like the Rust MCP text. The GUI doesn't show it.
  • ControlActionInfo.description carries it. action.list and action.describe add a description member when an action has one. cmux action describe prints the result generically, so the member is enough: human output shows an action.description row and --json has the field.
  • Rust MCP (action_tools.rs fn description): the action's own description comes first, then a blank line, then the existing boilerplate unchanged. If the description is missing or blank, the tool keeps the old text.
  • Every Cloud action in CloudActionCatalog (50 of them) gets 1-3 sentences. Each was checked against its handler (CloudHandlers*.swift, CloudGitHubHandlers.swift, CloudMove*.swift): the args, what a CLI/agent run gets back, the 25 s / 30 s waits, what answers at once before the work is done (fork, restore, rename, kill, resize, new-workspace), what only a person can run (bring-back), and what asks the person first (an agent's cloud move card, which ignores --path/--machine; an agent's or script's new-machine may ask).
  • Network and firewall actions take their id in the path argument. The CLI turns a relative --path into a path under the caller's folder, so these descriptions say --arg path=<id>, which passes the value as is. Group A2 owns renaming that key.

Choices

  • Localization: none, on purpose. Descriptions are agent-facing English, like the MCP boilerplate they head and the hard-coded network titles already in the catalog. No lint forbids it: check-l10n.sh checks only tables, and localize_changes.py only String(localized:) calls. Localizing 50 keys would cost about 131 lines each, about 6,500 lines across 20 locales, so this stays one PR.
  • Own table file (Catalog/CloudActionDescriptions.swift), applied in descriptors() the same way as waitsForResult. CloudActionCatalog.swift is at 305 of the 400-line Swift file cap. Putting the text inline would push it over and would touch every entry that #25217 and the sibling cloud PRs are editing. A descriptor that passes description: itself wins over the table.
  • Export unchanged. ActionSurfaceExport (plans/cmux-next/action-surfaces.json) does not include descriptions, so nothing needs regenerating. Adding them to the generated docs reference can be its own PR.
  • A command named in a description must be CLI-offered. A test enforces it: cmux cloud open-machine and machine-tools are GUI-only, so their descriptions point at new-terminal-on-machine and exec instead.

No capture: not a UI change. The text goes only into action.list / action.describe and the MCP tool descriptions; the GUI shows none of it.

Before / after (derived from the code: not a UI change)

cmux action describe cloud exec --json, abridged:

before: {"action":{"id":"cloudExec","title":"Run Command on Cloud Machine…","cli_name":"cloud exec","arguments":[{"name":"command","title":"Command","kind":"string","required":true}],"targets":["machine"],"requires":["cloudWorkspace"],...}}
after:  {"action":{"id":"cloudExec","title":"Run Command on Cloud Machine…","description":"Runs a shell command on the machine through the Cloud API and returns machine_id, exit_code, stdout and stderr; no tab opens. Needs --target machine:<id> and --command; the command stops after 25 s. A person's run from the palette types it into a new terminal tab instead.","cli_name":"cloud exec",...}}

MCP app_cloud_exec tool description:

before:
Run Command on Cloud Machine. cmux app action `cloudExec` (CLI: `cmux cloud exec`). Runs in the cmux app and waits until its work is done. An MCP run changes the user's focus only when focus is true or the action's purpose is focus. Needs: cloudWorkspace.

after:
Runs a shell command on the machine through the Cloud API and returns machine_id, exit_code, stdout and stderr; no tab opens. Needs --target machine:<id> and --command; the command stops after 25 s. A person's run from the palette types it into a new terminal tab instead.

Run Command on Cloud Machine. cmux app action `cloudExec` (CLI: `cmux cloud exec`). Runs in the cmux app and waits until its work is done. An MCP run changes the user's focus only when focus is true or the action's purpose is focus. Needs: cloudWorkspace.

Tests

  • CloudActionDescriptionTests: every Cloud action has a non-empty description, no table key is stale, ActionCatalog.all carries them, every cmux <noun> <verb> named is CLI-offered, and a machine command named carries --target machine:<id>.
  • ActionDescriptionWireTests: ControlActionInfo.json carries description, omits it when nil, and the registry's catalog takes it from the descriptor.
  • Rust cli/mcp/tests/action_descriptions.rs: the description comes first and the boilerplate follows; a missing or blank one keeps the old text.

Risk

Low. It adds one optional field to the wire, and older CLIs ignore unknown members. The MCP text changes only for actions that have a description, and the only behaviour change is tool description text. Overlaps: #25217 changes the newCloudMachine entry. When it lands, the newCloudMachine description gets its --persistent --size --name flags in a follow-up (or here if it lands first).

Agent session: Air-Blue

🤖 Generated with Claude Code