Quick Start — build an extension in 5 min
Build a Webbee extension in 5 minutes — install the Imperal SDK, write a typed handler, generate the manifest, smoke-test, and publish a deployed Hello World.
Build, test, and run a real extension in five minutes. By the end of this page, you'll have a working hello-world extension that the LLM can call when a user says "say hi to Alex".
Prerequisites
Python 3.11+ and pip. That's it. Editor of choice. No frameworks to install yet — the SDK ships everything you need.
Install the SDK
Pick your environment manager — venv (built-in) or uv (faster, recommended):
npm install imperal-sdkVerify it's there:
python -c "import imperal_sdk; print(imperal_sdk.__version__)"The version printed is whatever's currently on PyPI — see Changelog for what each release includes, or run pip install -U imperal-sdk later to pull the newest one.
Create your extension folder
mkdir hello-world && cd hello-world
touch app.py imperal.jsonThe two files that matter for any extension:
app.py— your handlers (Python)imperal.json— your manifest (auto-generated, but exists for the marketplace)
Write the handler
Paste this into app.py:
from imperal_sdk import Extension, ChatExtension, ActionResult, sdl
from pydantic import BaseModel, Field
# Pydantic params model — typed, auto-validated, schema sent to the agent.
class GreetParams(BaseModel):
name: str = Field(description="Person to greet")
# Typed return model. A read tool returns a typed SDL entity, so the
# platform reads the result as structured data (id / title / kind are the
# core SDL fields) and can resolve downstream chain references against it.
# The decorator declares it via data_model= (V23 — Typed Return Contract).
class Greeting(sdl.Entity):
text: str = Field(description="The greeting message")
# Your extension instance. The first positional argument is the app_id;
# display_name + description (≥40 chars) + icon + actions_explicit are
# all required by the federal extension contract (V14, V15, V19, V21).
ext = Extension(
"hello-world",
version="1.0.0",
display_name="Hello World",
description="Demo extension that greets people by name with a friendly message.",
icon="icon.svg",
actions_explicit=True,
capabilities=["hello-world:read"],
)
# A ChatExtension wraps your extension's chat surface — one per ext.
# tool_name= is the canonical registration key for this chat surface.
chat = ChatExtension(
ext,
tool_name="hello_world",
description="Hello World — friendly greetings.",
)
# Your handler — the agent calls this. Pydantic params are auto-detected
# from the type annotation, so no extra parameter is needed. The handler
# returns a typed ActionResult (V18) carrying an SDL entity, and a read
# tool declares its data_model (V23).
@chat.function(
"greet",
action_type="read",
description="Greet someone by name with a friendly message.",
data_model=Greeting,
)
async def greet(ctx, params: GreetParams) -> ActionResult:
return ActionResult.success(
Greeting(id=params.name, title=params.name, text=f"Hello, {params.name}! 🐝"),
summary=f"Greeted {params.name}",
)That's the entire extension.
What's actually happening
Extension("hello-world", ...)declares your extension to the platform. The first positional is the app_id — required, lowercase, hyphen-separated.ChatExtension(ext, tool_name="hello_world", description=...)registers the chat surface. One per extension.tool_name=is the canonical registration key for the surface.class Greeting(sdl.Entity)is the typed return shape.sdl.Entitygives you the coreid/title/kindfields for free; you add domain fields liketext. Returning an SDL entity (instead of a rawdict) is what lets the platform read the result as structured data and validate$REFchain references against its fields. See the SDL reference.@chat.function("greet", action_type="read", description=..., data_model=Greeting)registersgreetas a callable tool. Pydantic params are auto-detected from the typed function arg, so no extra parameter is needed. Areadtool declares itsdata_model(V23).- The handler returns
ActionResult.success(Greeting(...))— the V18 federal contract requires a typed return, and SDL makes that return a typed entity rather than a loose dict. The platform validates the agent's args against your params model and re-prompts the model up to twice on a validation failure with structured prose feedback, then your handler runs. actions_explicit=Trueis the federal-grade default: nothing fires until the user clearly asks.
Generate the manifest
imperal buildThis walks your app.py and produces imperal.json — the manifest the marketplace + Webbee read at install time. Look at it:
{
"manifest_schema_version": 3,
"name": "Hello World",
"display_name": "Hello World",
"description": "Demo extension that greets people by name with a friendly message.",
"icon": "icon.svg",
"actions_explicit": true,
"tools": [
{
"name": "greet",
"description": "Greet someone by name with a friendly message.",
"action_type": "read",
"params_schema": {
"type": "object",
"properties": {
"name": { "type": "string", "description": "Person to greet" }
},
"required": ["name"]
},
"return_schema": {
"x-sdl": "entity",
"type": "object",
"properties": {
"id": { "type": "string" },
"title": { "type": "string" },
"kind": { "type": "string" },
"text": { "type": "string", "description": "The greeting message" }
}
}
}
]
}Run a smoke test locally
The SDK ships a standalone test runner. No web-kernel required:
imperal testThis loads app.py, runs each registered handler against MockContext, and reports pass/fail. Same MockContext you'd use in pytest.
Publish through the Developer Portal
Open panel.imperal.io/developer in your browser and drag-and-drop your packaged extension. The portal:
- Re-runs the full federal validator set (V1–V24 + V31) server-side
- Queues your submission for review
- Notifies you when it's approved
Once accepted, your extension appears in the Marketplace. End users install it with one click.
Validators are strict
Most rules are ERROR-severity and block publish (a few, like the data_model recommendation for write/destructive tools, are advisory WARNs). If your manifest doesn't pass, the publish is rejected with a clear list of what to fix. See the validators reference.
Try it in the chat
After install, open panel.imperal.io and type:
You: say hi to Alex
Webbee: Hello, Alex! 🐝
The chat box turned natural language into a tool call (greet(name="Alex")), executed your Python, and rendered the result. That's the entire flow.
What just happened
User types "say hi to Alex"
↓
Webbee turns the message into a tool call: greet(name="Alex")
↓
Permissions and the typed args are checked for you
↓
Your greet() runs → Greeting(id="Alex", title="Alex", text="Hello, Alex! 🐝")
↓
Webbee shows the result in chatWhere to go next
Your First Real Extension
A real walkthrough — typed params, panels, audit, secrets. Builds on what you just did.
How it all works
Now that you've shipped one — see the full architecture from web-kernel down to your handler.
The Federal Contract
What every extension MUST satisfy to ship. Manifest schema, decorators, runtime invariants.
Recipes
Copy-paste examples — sending mail, querying DBs, multi-step chains, [BYOLLM](/en/reference/glossary/).
Long-running operations
Calling OpenAI or another slow API? The 30s ctx.http default is just one of three tiers. Learn ctx.background_task and @chat.function(background=True) before you hit the cap.
What is Imperal Cloud?
Imperal Cloud — the first ICNLI AI Cloud OS. Webbee 🐝 is its native agent: cloud-modular and Web 3.0-native, spanning your life and work.
Install the Imperal SDK
Install the Imperal SDK for Webbee extension development — set up Python 3.11+ on macOS, Linux, or Windows, create a virtualenv, and configure your editor.