con un clic
surface
// Learn how to present your work as a structured surface — a curated manifest of artifacts that the user's application renders in real time.
// Learn how to present your work as a structured surface — a curated manifest of artifacts that the user's application renders in real time.
Learn how to make widgets on canvas
Learn how to design and write custom agent templates (sub-agents) inside your workspace, so you can delegate specialized work to custom sub-agents.
Uplevel your researching abilities and learn how to research properly.
How authenticated HTTP calls to the Opal backend work — the host/guest architecture, fetchWithCreds, the fetch allowlist, and the OpalBackendClient migration. Read this before adding, modifying, or debugging any backend call.
Produce high-quality React apps from natural language descriptions.
Learn how to make widgets on canvas
| name | surface |
| title | Presenting Your Work via Surface |
| description | Learn how to present your work as a structured surface — a curated manifest of artifacts that the user's application renders in real time. |
| allowed-tools | ["files.*","events.*"] |
A surface is a structured manifest that tells the user's application what to display. It contains links to various files to present to the user and optionally, groups them into sections.
A surface is a living document you update as you work, so the user sees your progress in real time.
Every time you produce, update, or receive a meaningful artifact, follow these steps:
surface.json. Overwrite it with the complete current state of
what you want to present, including the new/updated artifact. Increment the
version counter.events_broadcast with type surface_updated. The
consumer application listens for surface_updated and re-reads
surface.json to update the display.surface.json is a single JSON file that you overwrite on every change.
{
"version": 1,
"title": "My Surface Title",
"sections": [{ "id": "main", "title": "Main Results" }],
"items": [
{
"id": "findings",
"title": "Research Findings",
"path": "research_notes.md",
"description": "Key findings from the analysis",
"section": "main"
}
]
}
| Field | Required | Description |
|---|---|---|
version | yes | Integer. Start at 1, increment on every write. |
title | no | Human-readable name for the surface. |
sections | no | Declared section groups. Omit if no grouping is needed. |
items | yes | Ordered list of content items. |
Sections are optional named groups. Items reference them by id.
| Field | Required | Description |
|---|---|---|
id | yes | Stable identifier, referenced by items. |
title | yes | Human-readable heading. |
description | no | Brief description of the section. |
active | no | When true, this section is selected by default in tabbed views. At most one section should be active. |
Each item represents one piece of content you want to present.
| Field | Required | Description |
|---|---|---|
id | yes | Stable identifier, unique within the surface. |
title | yes | Human-readable label. |
path | no | File path (workspace-relative). At least one of path or description must be present. |
description | no | Inline text — standalone content, preview, or metadata. At least one of path or description must be present. |
render | no | Rendering override. Use bundle for JS files that should render in a sandboxed iframe. |
role | no | Semantic role: primary (hero), supporting (detail), status (lightweight indicator). |
section | no | The section id this item belongs to. Ungrouped items go to a default section. |
files_write_fileThe path field in items uses the same workspace-relative format as
files_write_file. If you write a file with files_write_file using
research/findings.md, reference it in the surface as
"path": "research/findings.md".
surface.json in your writable directoryUse files_write_file with "file_name": "surface.json". If you are a
sub-agent with a scope restriction, write it within your assigned directory
(e.g., research/surface.json). The consumer discovers surface files by walking
the filesystem tree.
Every time you write surface.json, include all items you want displayed —
not just the ones that changed. If you previously had 3 items and now have 4,
write all 4. If you want to remove an item, omit it from the next write.
The version counter lets the consumer detect changes. Start at 1. Increment by
1 on every write, even if only the ordering changed.
After writing surface.json, call events_broadcast with:
type: surface_updatedmessage: brief description of what changed (e.g., "Added research findings")Not every item needs a file. Use description alone for lightweight status
indicators:
{
"id": "status",
"title": "Progress",
"description": "3 of 5 sources analyzed",
"role": "status"
}
If you produce a JavaScript bundle (e.g., an interactive chart), set
"render": "bundle" on the item. The consumer loads the JS in a sandboxed
iframe and discovers a companion CSS file by convention (same filename stem).
Don't wait until the end. The surface is most valuable when the user can see progress. A good pattern:
A research agent doing competitive analysis might produce this sequence:
After starting (version 1):
{
"version": 1,
"items": [
{
"id": "status",
"title": "Status",
"description": "Researching competitors — 3 searches in progress",
"role": "status"
}
]
}
After first results (version 2):
{
"version": 2,
"title": "Competitive Analysis",
"sections": [{ "id": "findings", "title": "Findings" }],
"items": [
{
"id": "market-overview",
"title": "Market Overview",
"path": "research/market_overview.md",
"description": "Current market landscape and key players",
"role": "primary",
"section": "findings"
},
{
"id": "status",
"title": "Status",
"description": "2 of 3 research threads complete",
"role": "status"
}
]
}
Final surface (version 3):
{
"version": 3,
"title": "Competitive Analysis",
"sections": [
{ "id": "findings", "title": "Findings" },
{ "id": "data", "title": "Supporting Data" }
],
"items": [
{
"id": "market-overview",
"title": "Market Overview",
"path": "research/market_overview.md",
"description": "Current market landscape and key players",
"role": "primary",
"section": "findings"
},
{
"id": "competitor-matrix",
"title": "Competitor Comparison",
"path": "research/competitor_matrix.md",
"description": "Feature-by-feature comparison of top 5 competitors",
"section": "findings"
},
{
"id": "raw-data",
"title": "Research Data",
"path": "research/raw_data.json",
"description": "Structured data from all sources",
"role": "supporting",
"section": "data"
}
]
}
Each version was followed by an events_broadcast with type surface_updated.