Imperal Docs
Getting Started

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-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
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=...) registers greet as 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 on ValidationError with structured prose feedback.
  • 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)
{
  "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 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 all 11 federal validators (V14–V22 + 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

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 chat

Where to go next

On this page