10 free hours of engineering. · 1-hour scoping call up front — we'll start this week.Claim hours →
The anatomy

What's in a module.

Every module has the same shape, so every engineer reads the next module fluently.

module.jsonManifest. Identity, version, roles, UI panel, table prefix, dependencies.
tools.py@platform_tool functions. The agent's view of your module.
routes.pyFastAPI router. Standard REST for the tenant UI.
repository.pyAsync SQLAlchemy data access, scoped to the tenant DB.
agent.yamlOptional: module-owned agent skills and tool bindings.
migrations/Alembic. Sequential. Auto-applied when a tenant subscribes.
module-ui.tsxTenant UI panel. Renders into the console's module slot.
module.json
{
  "code": "pm_monitor",
  "name": "PM Performance Monitor",
  "version": "1.4.0",
  "description": "Tracks PC and workstation fleet health.",
  "table_prefix": "pm_",
  "roles": ["operator", "supervisor", "admin"],
  "ui_panel": "module-ui.tsx",
  "depends_on": [],
  "agent_skills": ["pm.health_briefing"],
  "sandbox_demo": "sandbox/pm_demo_data.json"
}
cli
$ platform modules scaffold pm_monitor
  → wrote modules/pm_monitor/
  → added module.json, tools.py, routes.py, repository.py
  → added migrations/0001_initial.py
  → added frontend/modules/pm_monitor/module-ui.tsx

$ platform modules disable pm_monitor --tenant acme-ops
  → unsubscribed acme-ops
  → tools deregistered from agent context
  → UI panel hidden in tenant console
The CLI flow

From manifest to deployed feature.

01

Scaffold

platform modules scaffold pm_monitor — generates manifest, tools.py, routes.py, repository.py, ui panel stub, and initial migration.

02

Write tools

Decorate functions with @platform_tool. The platform turns them into agent tools, REST endpoints, and CLI verbs.

03

Sandbox preview

platform modules preview pm_monitor — runs in the sandbox tenant with seeded demo data. Agent included.

04

Pipeline

platform modules promote pm_monitor — runs the module through validation, migration dry-run, security checks, and tenant subscription.

05

Subscribe a tenant

platform tenants subscribe acme-ops pm_monitor — migrations apply, tools register, UI mounts on the next page load.

# tools.py — the only file that matters

from platform.sdk import platform_tool, ToolContext

@platform_tool(
    name="pm.machines.list",
    description="List monitored machines for the tenant.",
    roles=["operator", "supervisor"],
)
async def list_machines(ctx: ToolContext) -> list[dict]:
    repo = MachineRepo(ctx.tenant_db)
    machines = await repo.all()
    return [m.to_dict() for m in machines]

@platform_tool(name="pm.machine.restart", roles=["supervisor"])
async def restart_machine(ctx: ToolContext, machine_id: str) -> dict:
    # bridge tool — runs on customer site
    result = await ctx.bridge.dispatch("pm.machine.restart", machine_id)
    await ctx.audit.write("machine.restart", machine_id=machine_id)
    return result
Why this shape

What "@platform_tool" earns you for free.

Agent registration

Tool appears in the executor's registry on next request, scoped to the declared roles.

REST endpoint

Mirror endpoint mounted at /api/v1/{module_code}/tools/{name}. Same signature, same RBAC.

CLI verb

platform call pm.machines.list --tenant acme-ops — for ops and runbooks.

Audit row

Every invocation written to the platform audit log with the agent session id.

Tenant routing

ctx.tenant_db is pre-bound to the caller's tenant DB. You cannot accidentally cross.

RBAC enforcement

Roles checked before your function runs. Unauthorized callers get a 403, not a stack trace.

Bridge dispatch

Mark a tool 'bridge' and it runs at the customer site, not the cloud. Same decorator.

Typed schema

Function signature becomes the agent's JSON schema. Pydantic for complex returns.