Skip to content

Developing Built-in Plugins

Weave v0.1 supports only built-in plugins that are compiled, tested, and upgraded with the product release. There is no third-party marketplace, independent install/uninstall, version selection, artifact upload, WebAssembly (WASM), native subprocess, sandbox, or microservice runtime.

This boundary is intentional: the current priority is fast and reliable platform and agent releases. Agent capabilities that need an independent lifecycle may later use a project-specific worker protocol built with the Pi SDK, not the generic plugin runtime. That worker protocol is a Weave proposal, not a Pi SDK interface.

Runtime model

LocationFormRegistrationRelease unit
BackendIn-process Go implementing pluginsdk.BackendPlugininternal/backend/plugins/catalog/weave
DeviceIn-process Go implementing agentplugin.AgentPlugininternal/agent/plugins/catalog/weave-agent

The lifecycle is fixed:

compile-time catalog → Init → Start → Health → Stop (reverse order)

Registration order is startup order. An Init or Start failure is logged and isolated while the remaining built-ins continue.

Backend plugin

Implement a package under internal/backend/plugins/{plugin-id}/:

go
type Plugin struct {
    host pluginsdk.HostAPI
}

func New() *Plugin { return &Plugin{} }

func (p *Plugin) Manifest() pluginsdk.PluginManifest {
    return pluginsdk.PluginManifest{
        ID:          "example",
        Version:     "1.0.0",
        Description: "Example builtin module",
        Bus: &pluginsdk.BusCapability{
            Topics: []string{"evt.plugin.example.*"},
        },
        Capabilities: []pluginsdk.Capability{
            "bus.subscribe.evt.plugin.example.*",
        },
    }
}

func (p *Plugin) Init(ctx context.Context, host pluginsdk.HostAPI) error {
    p.host = host
    return nil
}

func (p *Plugin) Start(context.Context) error { return nil }
func (p *Plugin) Stop(context.Context) error  { return nil }
func (p *Plugin) Health() pluginsdk.HealthReport {
    return pluginsdk.HealthReport{Healthy: true}
}

PluginManifest contains only fields consumed by the platform:

  • identity: ID, Version, and Description
  • HostAPI declarations: HTTP, Bus, Metrics, and DB
  • control plane: Permissions, ConfigSchema, and Surfaces
  • config targeting: ApplyScopes and DefaultApplyTarget
  • security capabilities: Capabilities
  • optional device-local port: UIPort

It has no runtime tier, artifact location, dependency graph, or resource-budget declaration.

HostAPI boundary

A backend plugin uses platform services only through pluginsdk.HostAPI:

  • HTTP(): register routes under /api/v1/plugins/{plugin-id}/
  • Bus(): publish and subscribe to declared topics
  • Metrics(): register plugin-namespaced metrics
  • DB(): run plugin migrations and queries
  • Store(): use plugin-ID-namespaced key-value (KV) state
  • PluginConfig(), Shadow(), Secrets(), and Auditor()
  • HTTPClient(tenantID): make outbound requests checked against host capabilities

Register HTTP routes in Init, before the server starts listening. Put bus subscriptions and background work in Start.

Database and tenant isolation

Declaring DB requests database access; the composition root must also list the plugin ID as a trusted built-in.

Row-Level Security (RLS) protects tenant tables. Always access them through:

go
db := host.DB().WithTenant(tenantID)

The implementation uses transaction-local SET LOCAL app.tenant_id. Never use SET SESSION, and never import bun from a plugin.

HTTP and role-based access control (RBAC)

Declare routes and permissions in the manifest:

go
HTTP: &pluginsdk.HTTPCapability{Routes: []pluginsdk.RouteDecl{
    {
        Pattern:     "/items/{id}",
        Method:      "GET",
        Subresource: "item",
        Action:      "read",
    },
}},
Permissions: []pluginsdk.PluginPermissionDecl{
    {
        Subresource:  "item",
        Action:       "read",
        Description:  "Read example items",
        DefaultRoles: []string{"viewer", "operator", "tenant_admin"},
    },
},

Register the route in Init:

go
host.HTTP().Handle("GET /items/{id}", "item", "read", handler)

The RBAC resource is {plugin-id}/{subresource}. The manager seeds manifest permissions idempotently at boot.

Registration and tests

Add backend constructors only to internal/backend/plugins/catalog/catalog.go. Add device constructors only to internal/agent/plugins/catalog/catalog.go. Do not import concrete plugins from cmd/weave or cmd/weave-agent.

At minimum, run:

bash
go test ./internal/backend/plugin/... ./internal/backend/plugins/...
go test ./internal/agent/plugin/... ./internal/agent/plugins/...
go test ./...

If Protobuf changes, also run make proto and make web-proto.

Weave — IoT Device Management Platform