Authoring generative UI over AG-UI
SkillAI & modelsagui-author lets your AI build a live dashboard while it works, so you can follow progress and results visually instead of reading plain text. Once added, your AI can create dashboard screens and keep them updated in real time as a task runs. It comes from the cli-agent-orchestrator project by AWS Labs.
Available today. Use it from your connected AI after setup.
No other account needed.
Add the skill, then ask your AI to build a dashboard for its next task. It will create the screens and keep them up to date while it works.
Then ask your AI: use the Authoring generative UI over AG-UI skill
What your AI can do with it
- Create a live dashboard for any task
- Update the dashboard in real time as work happens
- Design the screens and layout it displays
- Show progress and results visually
What this skill tells your AI
The instructions your AI receives, as published by awslabs/cli-agent-orchestrator in skills/agui-author/SKILL.md and read by ahel’s review.
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 aValueError; nothing is rendered. propsmust be JSON-serializable and are bounded to 8 KB — an oversized or non-serializable payload is rejected at theemit_uiboundary (HTTP 400, the tool raises aValueError), 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— foldsSTATE_SNAPSHOT/STATE_DELTA+ youragent_cardemits into a live fleet hierarchy view.MultiAgentSessionTimeline— reconstructs delegation/message timeline fromTOOL_CALLlifecycle 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 and references/run-plane.md.
Gotchas
-
Emitting to a disabled surface — if
CAO_AGUI_ENABLEDis unset,emit_uireturns{"ok": false}gracefully. Don't treat this as an error or retry — it's a no-op by design. The fix: always checkokin the return but never fail on it. -
Props over 8 KB are rejected — the tool raises a
ValueErrorand nothing renders. The fix: reference file paths instead of embedding content. Keep props to metadata (paths, counts, labels). -
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. -
One intent per meaningful moment — emitting a
progresscard on every token or tool call floods the stream and degrades client rendering. The fix: emit at milestones (start, 25%, 50%, 75%, done) or once per logical phase. -
approval_cardis display-only today — it gives the operator an approve/reject affordance in the dashboard, but the action routes to the dashboard's command surface, not back to you. The fix: pair it with your provider's own wait-for-input mechanism (e.g. Kiro's trust prompts, Claude Code's permission dialog). -
Off-list components are refused server-side — the allow-list is fixed (
approval_card,choice_prompt,diff_summary,progress,metric,agent_card). A typo or new component name returns HTTP 400. The fix: use only the six listed names; check spelling.
Verifying locally
# 1. Server with the surface on
CAO_AGUI_ENABLED=true uv run cao-server
# 2. Watch the stream (SSE frames print as they arrive)
curl -N 'http://localhost:9889/agui/v1/stream'
# 3. Emit from anywhere (the MCP tool does exactly this)
curl -sX POST http://localhost:9889/agui/v1/emit_ui \
-H 'Content-Type: application/json' \
-d '{"component":"progress","props":{"label":"demo","value":0.5}}'
A GENERATIVE_UI frame with your component appears on the stream; an off-list
component is refused with HTTP 400.
See also
examples/ag-ui/ag-ui-dashboard/— a runnable demo (run.sh+showcase.sh) that drives all six components live and shows the off-list refusal.docs/agui.md— the AG-UI stream and generative-UI reference.cao-mcp-appsskill — operate and extend the MCP Apps surface that renders youremit_uiintents inside host dashboards (Claude Desktop, VS Code, etc.).mcp-apps-builderskill — build new MCP App views that consume the AG-UI stream your emits feed into.
Signals
- GitHub stars
- 1k
- Forks
- 266
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
agui-author- Source
- github.com/awslabs/cli-agent-orchestrator