Quick Start
From zero to a deployed Hello World extension in 5 minutes
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
from pydantic import BaseModel, Field
# Pydantic params model — typed, auto-validated, schema sent to LLM.
class GreetParams(BaseModel):
name: str = Field(description="Person to greet")
# 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.
chat = ChatExtension(
ext,
tool_name="hello_world",
description="Hello World — friendly greetings.",
)
# Your handler — the LLM calls this. Pydantic params are auto-detected
# from the type annotation, so no extra `pydantic_param=` argument is
# needed. The handler returns a typed ActionResult (V18 federal).
@chat.function(
"greet",
action_type="read",
description="Greet someone by name with a friendly message.",
)
async def greet(ctx, params: GreetParams) -> ActionResult:
return ActionResult.success(
data={"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 web-kernel. The first positional is the app_id — required, lowercase, hyphen-separated.ChatExtension(ext, tool_name=..., description=...)registers the chat tool the LLM sees. One per extension.@chat.function("greet", action_type="read", description=...)registersgreetas a callable tool. Pydantic params are auto-detected from the typed function arg, so no extra kwarg is needed.- The handler returns
ActionResult.success(...)— V18 federal contract requires a typed return. The SDK auto-validates against the LLM's args and retries up to twice onValidationErrorwith structured prose feedback. 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:
{
"schema_version": 3,
"name": "hello-world",
"display_name": "Hello World",
"description": "Demo: send a friendly greeting by name",
"icon": "hand-wave",
"actions_explicit": true,
"functions": [
{
"name": "greet",
"description": "Greet someone by name with a friendly message.",
"params_schema": {
"type": "object",
"properties": {
"name": { "type": "string", "description": "Person to greet" }
},
"required": ["name"]
}
}
]
}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 all 11 federal validators (V14–V22 + 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
All 11 validators are ERROR-severity. 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's intent classifier picks: { tool: "hello-world.greet", args: {"name": "Alex"} }
↓
Pre-flight: tenant_id, user_id, action_type checks pass
↓
SDK auto-validates: GreetParams(name="Alex") — OK
↓
Your greet() runs → { "text": "Hello, Alex! 🐝" }
↓
Webbee renders the result to 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.