Imperal Docs
Getting Started

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-sdk

Verify 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.json

The two files that matter for any extension:

🐍app.py
📋imperal.json
  • app.py — your handlers (Python)
  • imperal.json — your manifest (auto-generated, but exists for the marketplace)

Write the handler

Paste this into app.py:

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.Entity gives you the core id / title / kind fields for free; you add domain fields like text. Returning an SDL entity (instead of a raw dict) is what lets the platform read the result as structured data and validate $REF chain references against its fields. See the SDL reference.
  • @chat.function("greet", action_type="read", description=..., data_model=Greeting) registers greet as a callable tool. Pydantic params are auto-detected from the typed function arg, so no extra parameter is needed. A read tool declares its data_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=True is the federal-grade default: nothing fires until the user clearly asks.

Generate the manifest

imperal build

This walks your app.py and produces imperal.json — the manifest the marketplace + Webbee read at install time. Look at it:

imperal.json (excerpt)
{
  "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 test

This 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:

  1. Re-runs the full federal validator set (V1–V24 + V31) server-side
  2. Queues your submission for review
  3. 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 chat

Where to go next

On this page