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