/agui-author
Author live dashboard UI from an agent via the `emit_ui` MCP tool. Emit
$ npx -y skills add awslabs/cli-agent-orchestrator --skill agui-author --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/agui-author
Context preview
The summary Claude sees to decide when to auto-load this skill.
Author live dashboard UI from an agent via the `emit_ui` MCP tool. Emit
SKILL.md
agui-author.SKILL.mdname: agui-author
description: Author live dashboard UI from an agent via the `emit_ui` MCP tool. Emit
one of six allow-listed components (approval_card, choice_prompt, diff_summary,
progress, metric, agent_card) with JSON props and it renders in any AG-UI client
watching the fleet. Use when you want the operator to see a decision, a diff, or a
status readout instead of scrolling terminal text. Arbitrary HTML/markup is refused.
Authoring generative UI over AG-UI
CAO exposes an **AG-UI** stream (`GET /agui/v1/stream`) that any dashboard — CopilotKit, the AG-UI Dojo, or a plain `EventSource` — renders without CAO-specific code. As an agent you can push a **declarative UI intent** onto that stream with the `emit_ui` MCP tool. The operator sees a rendered card, not raw text — and because every provider's intents render uniformly, they can't tell (and don't need to) which CLI agent produced which card.
The surface must be enabled on the server (`CAO_AGUI_ENABLED=true` or `CAO_MCP_APPS_ENABLED=true` — the two surfaces share one event source). When it is disabled, `emit_ui` returns `{"ok": false, "reason": "AG-UI surface disabled…"}` — treat that as a no-op, not an error.
Safety model (why this is always safe to call)
You may emit **only** a closed allow-list of named components with JSON props. There is **no HTML, no script, no `eval`, no iframe**. The intent is validated **server-side** against the allow-list before it reaches the stream:
- An **off-list** component (e.g. `iframe`, `script`) is **refused** — the tool
raises a `ValueError`; nothing is rendered.
- `props` must be **JSON-serializable** and are **bounded to 8 KB** — an oversized
or non-serializable payload is **rejected** at the `emit_ui` boundary (HTTP 400, the tool raises a `ValueError`), so a bad payload never reaches the bus.
- If the AG-UI surface is disabled on the server, the tool **degrades gracefully**
(no error) — so calling it is never fatal.
- The AG-UI stream is **metadata-only by contract**: never put message bodies,
credentials, or file contents in props. Reference paths, not contents.
The tool
emit_ui(component: str, props: dict) -> {"ok", "event_id", "component"}`component` must be one of: `approval_card`, `choice_prompt`, `diff_summary`, `progress`, `metric`, `agent_card`.
When to use which component
Props below are what a conformant client renderer will display; unknown extra keys are ignored, not refused.
| Component | Use it when… | Props | |---|---|---| | `approval_card` | you need a human to approve/reject a risky action before you proceed | `title` (str), `detail` (str, optional), `risk` (`"low"`/`"medium"`/`"high"`, optional) | | `choice_prompt` | you want the operator to pick among options | `question` (str), `choices` (list of `{"label", "value"}` or plain strings) | | `diff_summary` | you changed files and want a compact review | `title` (str), `files` (list of `{"path", "additions", "deletions"}`) | | `progress` | a long step is running | `label` (str), `value` (0.0–1.0; omit for an indeterminate bar) | | `metric` | you want to surface a single number | `label` (str), `value` (str/number), `unit` (str, optional) | | `agent_card` | you want to advertise your identity/status in the fleet view | `name` (str), `provider` (str), `status` (str, optional) |
Examples
# Gate a risky action on human approval.
emit_ui("approval_card", {
"title": "Deploy to production?",
"detail": "3 files changed, 1 DB migration",
"risk": "high",
})
# Ask the operator to choose.
emit_ui("choice_prompt", {
"question": "Which base branch?",
"choices": [{"label": "main", "value": "main"},
{"label": "release", "value": "release"}],
})
# Summarize a change set.
emit_ui("diff_summary", {
"title": "Refactor auth",
"files": [{"path": "security/auth.py", "additions": 74, "deletions": 3}],
})
# Show progress / a metric / your identity.
emit_ui("progress", {"label": "Indexing repository", "value": 0.42})
emit_ui("metric", {"label": "tokens used", "value": 12840, "unit": "tok"})
emit_ui("agent_card", {"name": "reviewer", "provider": "claude_code", "status": "working"})L2 constructs (Phase 2)
The AG-UI surface also exposes **L2 constructs** — higher-level projections that fold the raw event stream into structured views. As an agent you don't author L2 constructs, but you should know they exist because your `emit_ui` intents feed them:
- **`SupervisorDashboardStream`** — folds `STATE_SNAPSHOT`/`STATE_DELTA` + your
`agent_card` emits into a live fleet hierarchy view.
- **`MultiAgentSessionTimeline`** — reconstructs delegation/message timeline
from `TOOL_CALL` lifecycle events.
- **`AgentHandoffWithApproval`** — the full interrupt lifecycle: provider prompt
→ reason classification → interrupt → approve/deny/edit → delivery.
- **`CrossProviderStateSync`** — convergence proof across providers.
The **run plane** (`POST /agui/v1/run`) streams these as stock AG-UI wire frames. Interrupts (approval prompts) route through `POST /agui/v1/interrupts/{id}/resume`.
For details: [references/l2-constructs.md](references/l2-constructs.md) and [references/run-plane.md](references/run-plane.md).
Gotchas
1. **Emitting to a disabled surface** — if `CAO_AGUI_ENABLED` is unset, `emit_ui` returns `{"ok": false}` gracefully. Don't treat this as an error or retry — it's a no-op by design. The fix: always check `ok` in the return but never fail on it.
2. **Props over 8 KB are rejected** — the tool raises a `ValueError` and nothing renders. The fix: reference file paths instead of embedding content. Keep props to metadata (paths, counts, labels).
3. **No HTML sink exists** — strings in props render as plain text. Attempting to smuggle markup through props (e.g. `<script>`, `<iframe>`) won't render and looks broken. The fix: use structured props, not markup.
4. **One intent per meaningful moment** — emitting
Read more
name: agui-author description: Author live dashboard UI from an agent via the `emit_ui` MCP tool. Emit one of six allow-listed components (approval_card, choice_prompt, diff_summary, progress, metric, agent_card) with JSON props and it renders in any AG-UI client watching the fleet. Use when you want the operator to see a decision, a diff, or a status readout instead of scrolling terminal text. Arbitrary HTML/markup is refused.
Authoring generative UI over AG-UI
CAO exposes an **AG-UI** stream (`GET /agui/v1/stream`) that any dashboard — CopilotKit, the AG-UI Dojo, or a plain `EventSource` — renders without CAO-specific code. As an agent you can push a **declarative UI intent** onto that stream with the `emit_ui` MCP tool. The operator sees a rendered card, not raw text — and because every provider's intents render uniformly, they can't tell (and don't need to) which CLI agent produced which card.
The surface must be enabled on the server (`CAO_AGUI_ENABLED=true` or `CAO_MCP_APPS_ENABLED=true` — the two surfaces share one event source). When it is disabled, `emit_ui` returns `{"ok": false, "reason": "AG-UI surface disabled…"}` — treat that as a no-op, not an error.
Safety model (why this is always safe to call)
You may emit **only** a closed allow-list of named components with JSON props. There is **no HTML, no script, no `eval`, no iframe**. The intent is validated **server-side** against the allow-list before it reaches the stream:
- An **off-list** component (e.g. `iframe`, `script`) is **refused** — the tool
raises a `ValueError`; nothing is rendered.
- `props` must be **JSON-serializable** and are **bounded to 8 KB** — an oversized
or non-serializable payload is **rejected** at the `emit_ui` boundary (HTTP 400, the tool raises a `ValueError`), so a bad payload never reaches the bus.
- If the AG-UI surface is disabled on the server, the tool **degrades gracefully**
(no error) — so calling it is never fatal.
- The AG-UI stream is **metadata-only by contract**: never put message bodies,
credentials, or file contents in props. Reference paths, not contents.
The tool
emit_ui(component: str, props: dict) -> {"ok", "event_id", "component"}`component` must be one of: `approval_card`, `choice_prompt`, `diff_summary`, `progress`, `metric`, `agent_card`.
When to use which component
Props below are what a conformant client renderer will display; unknown extra keys are ignored, not refused.
| Component | Use it when… | Props | |---|---|---| | `approval_card` | you need a human to approve/reject a risky action before you proceed | `title` (str), `detail` (str, optional), `risk` (`"low"`/`"medium"`/`"high"`, optional) | | `choice_prompt` | you want the operator to pick among options | `question` (str), `choices` (list of `{"label", "value"}` or plain strings) | | `diff_summary` | you changed files and want a compact review | `title` (str), `files` (list of `{"path", "additions", "deletions"}`) | | `progress` | a long step is running | `label` (str), `value` (0.0–1.0; omit for an indeterminate bar) | | `metric` | you want to surface a single number | `label` (str), `value` (str/number), `unit` (str, optional) | | `agent_card` | you want to advertise your identity/status in the fleet view | `name` (str), `provider` (str), `status` (str, optional) |
Examples
# Gate a risky action on human approval.
emit_ui("approval_card", {
"title": "Deploy to production?",
"detail": "3 files changed, 1 DB migration",
"risk": "high",
})
# Ask the operator to choose.
emit_ui("choice_prompt", {
"question": "Which base branch?",
"choices": [{"label": "main", "value": "main"},
{"label": "release", "value": "release"}],
})
# Summarize a change set.
emit_ui("diff_summary", {
"title": "Refactor auth",
"files": [{"path": "security/auth.py", "additions": 74, "deletions": 3}],
})
# Show progress / a metric / your identity.
emit_ui("progress", {"label": "Indexing repository", "value": 0.42})
emit_ui("metric", {"label": "tokens used", "value": 12840, "unit": "tok"})
emit_ui("agent_card", {"name": "reviewer", "provider": "claude_code", "status": "working"})L2 constructs (Phase 2)
The AG-UI surface also exposes **L2 constructs** — higher-level projections that fold the raw event stream into structured views. As an agent you don't author L2 constructs, but you should know they exist because your `emit_ui` intents feed them:
- **`SupervisorDashboardStream`** — folds `STATE_SNAPSHOT`/`STATE_DELTA` + your
`agent_card` emits into a live fleet hierarchy view.
- **`MultiAgentSessionTimeline`** — reconstructs delegation/message timeline
from `TOOL_CALL` lifecycle events.
- **`AgentHandoffWithApproval`** — the full interrupt lifecycle: provider prompt
→ reason classification → interrupt → approve/deny/edit → delivery.
- **`CrossProviderStateSync`** — convergence proof across providers.
The **run plane** (`POST /agui/v1/run`) streams these as stock AG-UI wire frames. Interrupts (approval prompts) route through `POST /agui/v1/interrupts/{id}/resume`.
For details: [references/l2-constructs.md](references/l2-constructs.md) and [references/run-plane.md](references/run-plane.md).
Gotchas
1. **Emitting to a disabled surface** — if `CAO_AGUI_ENABLED` is unset, `emit_ui` returns `{"ok": false}` gracefully. Don't treat this as an error or retry — it's a no-op by design. The fix: always check `ok` in the return but never fail on it.
2. **Props over 8 KB are rejected** — the tool raises a `ValueError` and nothing renders. The fix: reference file paths instead of embedding content. Keep props to metadata (paths, counts, labels).
3. **No HTML sink exists** — strings in props render as plain text. Attempting to smuggle markup through props (e.g. `<script>`, `<iframe>`) won't render and looks broken. The fix: use structured props, not markup.
4. **One intent per meaningful moment** — emitting
CLI Agent Orchestrator (CAO) coordinates multiple AI coding CLIs so a supervisor can delegate work to specialist agents in parallel or sequence. 📚 Documentation — guides, reference, and two interactive courses.
Other skills on cli-agent-orchestrator.
- /cao-agent-routing
Find and select the best installed CAO agent profile for a task before
Open skill - /cao-learning
Report task outcomes and distill lessons so the team improves across
Open skill - /cao-mcp-apps
Enable, operate, and extend CAO's MCP Apps surface — the host-rendered fleet dashboard visible inside MCP App hosts (Claude Desktop, ChatGPT, VS Code Copilot, Goose, Postman). Use when the user says "enable MCP Apps in CAO", "the ui://cao views aren't rendering", "rebuild MCP
Open skill - /cao-memory
Store, recall, and forget durable facts with CAO memory — user preferences,
Open skill - /cao-plugin
Create a new CAO (CLI Agent Orchestrator) plugin. Use this skill whenever the user wants to add a plugin that reacts to CAO lifecycle or messaging events, scaffold a plugin package, understand plugin requirements, or integrate an external system (Discord, Slack, dashboards,
Open skill - /cao-provider
Create a new CLI agent provider for CAO (CLI Agent Orchestrator). Use this skill whenever the user wants to add support for a new CLI-based AI agent (e.g., a new coding assistant CLI), integrate a new provider, or scaffold a provider implementation. Also use when the user asks
Open skill

