ERI Specification — Embedded Result Interface
Quick Start — Ship in under a day
- Create an embed page — HTTPS, reads state from URL params (10-line example · hello-world.html)
- Write a
skill.md— tell the Agent how to call your API and construct the embed URL (template · real example) - Ship it — works on most platforms today. No SDK, no approval cycle.
1. Definition · Checklist · 2. Workflow · 3. Conversation Flow · 4. Skill Authoring · 5. Embed Page · 6. Security · 7. Levels · 8. Use Cases · 9. Limitations · 10. Error Handling · 11. Landscape · Governance
1. Definition
AI Agents output text. ERI (Embedded Result Interface) lets them embed interactive UI instead — using only a skill.md and a URL.
Design principles: no new protocol, no runtime dependency, works on any platform that renders iframes — build it once, deploy it everywhere.
| Role | Responsibility |
|---|---|
| Agent | Understands intent, calls API, constructs embed URL, outputs UI |
| Provider | Writes skill.md and provides an HTTPS embed page (see minimal example) |
| Platform | Renders iframes (most already do) |
Snapshot-based — the Agent does not monitor the output UI. Each new instruction generates a fresh embed (see Section 3). The third-party app runs independently — it only needs to provide an HTTPS page.
Conformance — an implementation is ERI-compliant at Level 1 when: (1) the Provider serves an HTTPS embed page that reads initial state from URL parameters; (2) the Agent follows the four-step workflow; (3) the Platform renders the iframe with sandbox="allow-scripts". No registration, no certification.
Level 1 Self-Certification Checklist
- Embed page served over HTTPS
- Reads initial state from URL hash or query params
- Self-contained (no host JS/CSS dependencies)
- Handles invalid/missing URL params gracefully
skill.mdhas frontmatter +## Workflow- Agent calls API server-side (not from user's browser)
- Agent includes plain text fallback
- Iframe uses
sandbox="allow-scripts"
Add a badge to your README: [](https://github.com/2234839/eri-spec)
2. Workflow
Steps 2 and 4 involve external systems:
Call API Agent — The Agent calls the third-party app's API from the server side (not from the user's browser). Standard HTTP, structured JSON. No CORS (Cross-Origin Resource Sharing) configuration needed.
POST /api/calculate
Body: {"expr": "99.5 * 3"}
Response: {"expr": "99.5 * 3", "result": 298.5}
Output UI Agent Platform — The Agent outputs an iframe. Most web-based Agent UIs render iframes with automatic sandbox attributes. Unsupported platforms degrade to plain text.
<iframe sandbox="allow-scripts" src="https://app.example.com/embed#encoded_data" width="100%" height="300"></iframe>
3. Conversation Flow
Iframe edits don't notify the Agent. On each new turn:
- Agent reads previous results from context
- Combines new input with context, calls API again
- Outputs a fresh embed (old one is not updated)
Context comes from the Agent platform's conversation history — the Agent sees its own previous text output (including API results). No special state mechanism needed.
User: "Calculate 3 items at $99.5 each"
Agent: Calls API → outputs iframe showing 99.5 * 3 = 298.5
(user modifies values in the iframe — Agent is unaware)
User: "Add 8% tax"
Agent: Reads context (last result: 298.5), calls API with 298.5 * 1.08
→ outputs new iframe showing 298.5 * 1.08 = 322.38
4. Skill Authoring Provider
Place a skill.md file where the Agent can discover it — custom instructions, GPT actions, Claude skill files, or any platform-provided mechanism for defining Agent behavior. The format is the same across all platforms.
A minimal ERI Skill:
---
name: tool-name
description: What this Skill does
---
## Workflow
1. Extract parameters from user input
2. Call API: POST https://api.example.com/calculate
Body: {"expr": "expression"}
3. Encode API response as URL hash
4. Output as iframe: https://app.example.com/embed#<encoded_response>
(fallback to plain text if platform cannot render)
The frontmatter (name, description) follows standard YAML convention. The body section heading (## Workflow, ## 工作流, or any heading you choose) and its content are freeform — write in whatever natural language the Agent understands. The format is plain Markdown.
| Platform | Where to place skill.md content |
|---|---|
| ChatGPT | Custom Instructions, or GPT Actions in a custom GPT |
| Claude | Claude Code: CLAUDE.md or .claude/ skill files. Claude.ai: Project instructions. |
| Gemini | System Instructions (Gemini API or Google AI Studio) |
| Custom Agent | Any mechanism for defining Agent behavior (system prompt, config file, etc.) |
Complete examples: NoteCalc (real implementation) · Template (copy and fill in).
5. Embed Page Requirements Provider
| Requirement | Description |
|---|---|
| HTTPS | Must serve over HTTPS |
| URL param reading | Read initial state from URL hash (recommended) or query params. Recommended encoding: encodeURIComponent(JSON.stringify(data)) — but any format the embed page understands is valid. |
| Self-contained | Handle all interactions internally. No host JS/CSS dependencies. Responsive layout. |
| Accessibility (recommended) | ARIA labels, keyboard navigation, focus management. Follow WCAG guidelines where practical. |
| Level 2 bridge (optional) | Implement ui/* JSON-RPC bridge for bidirectional communication — see Section 7 |
| Theme adaptation (optional) | Read URL params or host colors to match light/dark theme |
| Iframe sizing (recommended) | Design for a responsive width (agents typically set width="100%"). Default height of 300–400px works for most embeds. For Level 2, use the ui/message JSON-RPC method to dynamically adjust height. |
Minimal Embed Page Example
The Agent calls the API server-side and passes the result to the embed page via URL hash. The page only renders — no computation inside the iframe.
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1"></head>
<body>
<div id="out" style="font-size:24px"></div>
<script>
const raw = decodeURIComponent(location.hash.slice(1));
try {
const data = raw ? JSON.parse(raw) : null;
document.getElementById('out').textContent =
data ? data.expr + ' = ' + data.result : 'No data';
} catch (e) {
document.getElementById('out').textContent = 'Unable to load results.';
}
</script>
</body>
</html>
This minimal example renders a result, handles empty state, and catches malformed data. For a working page you can open right now, see hello-world.html — it reads a name from the URL hash and lets the user edit it. A production embed page adds responsive layout, input validation, and theme adaptation.
6. Security
| Role | Rules |
|---|---|
| Platform | Add sandbox="allow-scripts" to iframes. Warning: adding allow-same-origin together with allow-scripts lets the iframe access its own origin's cookies and storage — only use it if the embed page needs its own cookies/storage. The iframe still cannot access the parent page across origins. Never pass user credentials to third-party iframes. If the platform uses Content Security Policy (CSP), add the embed page's origin to frame-src. |
| Provider | Don't rely on document.cookie (may be unavailable in sandbox). Don't use window.opener or window.top for navigation/DOM access (window.parent.postMessage for Level 2 is safe). Sanitize all inputs against XSS (Cross-Site Scripting). Never place PII (Personally Identifiable Information) or sensitive data in URL hash/query params — URLs are logged in browser history, proxy logs, and referrer headers. For sensitive data, pass a short-lived token in the URL and fetch actual data via an API call inside the embed page. |
7. Progressive Levels
| Level | Capability | Who needs to act |
|---|---|---|
| 0 | Plain text output | Agent only — nothing else needed |
| 1 | iframe embedding | Provider embed page + Platform iframe support (most already have this) |
| 2 | Bidirectional communication | Level 1 + Provider Platform both implement MCP Apps host bridge (ui/* JSON-RPC 2.0 over postMessage) |
Use Level 2 when the platform must react to user edits inside the iframe (e.g. auto-save, live previews). ERI Level 2 uses the same ui/* JSON-RPC 2.0 bridge defined by MCP Apps — no custom message format. This means an ERI Level 2 embed page is directly compatible with MCP Apps–enabled platforms.
| Method | Direction | Purpose |
|---|---|---|
ui/initialize | platform → iframe | Handshake: platform sends session info and capabilities |
ui/notifications/tool-input | platform → iframe | Delivers tool call input arguments to the iframe |
ui/notifications/tool-result | platform → iframe | Delivers tool execution result back to the iframe |
tools/call | iframe → platform | iframe requests the platform to call a tool |
ui/message | iframe → platform | iframe sends a follow-up message (e.g. user edited a value) |
/** Platform → iframe: initialize on load */
window.addEventListener("message", (event) => {
if (event.origin !== "https://agent-platform.example.com") return;
const { method, params } = event.data;
if (method === "ui/initialize") {
// Store session info (params.sessionId, params.capabilities)
}
});
/** iframe → Platform: user edited a value (notification, no response) */
window.parent.postMessage({
jsonrpc: "2.0",
method: "ui/message",
params: {
content: { type: "text", text: "User changed items.price to 199" }
}
}, "https://agent-platform.example.com");
/** iframe → Platform: request a tool call (expects response with matching id) */
window.parent.postMessage({
jsonrpc: "2.0",
id: 1,
method: "tools/call",
params: { name: "updateCart", arguments: { items: [...], total: 597 } }
}, "https://agent-platform.example.com");
// Platform responds: { jsonrpc: "2.0", id: 1, result: { content: [...] } }
8. Use Cases
Fits — any scenario where the user wants to tweak the Agent's output:
| Scenario | What the embed page does |
|---|---|
| Calculator / spreadsheet | Renders expressions and results; user edits values inline |
| Chart / dashboard | Renders a visualization; user adjusts filters or date range |
| Map / location | Renders an interactive map; user pans, zooms, or selects a pin |
| Form / survey builder | Renders a preview; user reorders fields or changes options |
| Document editor | Renders formatted content; user adjusts layout or typography |
| Design tool | Renders a preview; user tweaks colors, spacing, or dimensions |
| Code playground | Renders code with syntax highlighting; user edits and sees output |
Does not fit: Pure information queries (no interaction needed), long-form text generation, real-time sync (ERI is snapshot-based).
9. Limitations
- Snapshot-based — each turn produces a fresh embed; Agent cannot observe in-iframe edits (Section 1). Level 2
ui/*JSON-RPC enables bidirectional sync (Section 7). - Page load per turn — each user turn triggers a fresh iframe load. For heavy embed pages, this causes a visible reload. Keep embed pages lightweight.
- Limited device access — sandboxed iframes have restricted access to device APIs (GPS, camera, microphone, bluetooth). Expect standard web capabilities only.
- URL size limits — state encoded in the URL hash is subject to browser URL length limits (~2KB safe, ~8KB max per RFC 3986 practical limits). For larger datasets, use the large data pattern.
- No offline support — embed pages require network access. If the user loses connectivity, the iframe cannot render.
Large Data Pattern
When the API response exceeds URL size limits, use a token-based approach instead of encoding data in the URL:
/** Agent: call API, receive a result token instead of full data */
POST /api/report
Response: {"resultToken": "abc123", "summary": "Q1 revenue: $1.2M"}
/** Agent: embed with token, include text summary as fallback */
<iframe src="https://app.example.com/embed#abc123" ...></iframe>
/** Embed page: fetch full data using the token */
const token = decodeURIComponent(location.hash.slice(1));
const data = await fetch('/api/results/' + token).then(r => r.json());
10. Error Handling
| Failure | Agent behavior |
|---|---|
| API returns error or timeout | Report the error in plain text. Do not embed an iframe with error state. |
| Embed URL unreachable | Output plain text fallback. The user still receives the API result. |
| Platform blocks iframe | Degrade to plain text — the Agent's text output remains visible. |
| URL-encoded data exceeds limits | Provider should design embed page to accept a short token and fetch data server-side. |
ERI's text fallback is not a bug — it's the baseline. Every ERI output should include a text summary so the user always gets value regardless of iframe rendering.
11. Industry Landscape
ERI operates at the output layer — how the Agent presents results to the user. It is complementary to tool invocation protocols (MCP, Function Calling, REST) which operate at the input layer — how the Agent calls external systems. The spec is agnostic about how the API is called.
Three approaches exist for interactive Agent output:
| ERI | MCP Apps | A2UI (Google) | |
|---|---|---|---|
| How | Embed via URL (iframe) | Sandboxed iframe mini-apps | Declarative JSON UI schema |
| You build | HTTPS embed page + skill.md | MCP-compatible app package | Component catalog + A2UI schema |
| Platform needs | Render iframes (already done) | MCP runtime | A2UI renderer |
| Works today? | Yes — most platforms | MCP-enabled only | A2UI-enabled only |
| Capabilities | Full web platform | Platform APIs, real-time sync | Native rendering |
MCP Apps and A2UI offer richer capabilities (native APIs, real-time sync, platform integrations) — ERI trades those for universal availability today. Since MCP Apps also uses sandboxed iframes and the same ui/* bridge (Section 7), ERI embed pages carry forward unchanged.
What ERI adds over a plain ad-hoc iframe:
| Without ERI | With ERI | |
|---|---|---|
| Discovery | Agent doesn't know your app exists | skill.md tells the Agent when and how to use your app |
| API contract | Each Agent–provider pair is custom-built | Standardized: call API → encode result → embed URL |
| Fallback | iframe fails = broken output | Agent falls back to plain text automatically |
| Cross-platform | Prompt-engineer per Agent platform | One skill.md works on ChatGPT, Claude, Gemini |
Normative References
- HTML Living Standard —
<iframe>element andsandboxattribute - HTML Living Standard —
Window.postMessage()API - RFC 3986 — Uniform Resource Identifier (URI): Generic Syntax
- Content Security Policy Level 3 —
frame-srcdirective - JSON-RPC 2.0 Specification — Level 2 message transport format
- MCP Apps Host Bridge —
ui/*JSON-RPC methods for Level 2 bidirectional communication
Governance
ERI is a community specification maintained by the original author. Level 1 core workflow is frozen — no breaking changes. Additive changes (new use cases, Level 2 message types) follow an open proposal process via GitHub Issues. All substantive changes require a Pull Request with review. See CONTRIBUTING.md for details.