Skip to main content
Version: v0.9.0a2
Integrator

Plugin Author Guide

5 min readPlugin authorv0.9.0aN alpha

What this guide covers

Build a minimal Stigmem plugin against the stable 22-hook surface introduced for the v0.9.0a1 architecture-in-flight line. A plugin package contributes a PluginManifest, declares the capabilities it needs, registers hook handlers, and is loaded at node startup through the stigmem.plugins Python entry point group.

Audience: Developers writing opt-in Stigmem plugins for the v0.9.0aN alpha series.

The default install still runs without plugins.

Production nodes should only load plugins that have passed the signing and trusted-publisher checks described below.

Minimal package layoutโ€‹

example-stigmem-plugin/
pyproject.toml
src/
example_stigmem_plugin/
__init__.py
tests/
test_plugin.py

pyproject.toml declares the plugin entry point:

[project]
name = "example-stigmem-plugin"
version = "1.0.0"
requires-python = ">=3.12"
dependencies = [
"stigmem-node>=0.9.0a1",
]

[project.entry-points."stigmem.plugins"]
example_stigmem_plugin = "example_stigmem_plugin:plugin_manifest"

The entry point value must resolve to a zero-argument callable that returns a PluginManifest.

Manifest and hook handlersโ€‹

from __future__ import annotations

from stigmem_node.plugins import Allow, Deny, PluginContext, PluginManifest

BLOCKED_SOURCE = "agent:blocked"


def pre_assert_authorize(
_ctx: PluginContext,
*,
source: str | None = None,
**_: object,
) -> Allow | Deny:
if source == BLOCKED_SOURCE:
return Deny("source is blocked by example-stigmem-plugin")
return Allow()


def post_assert_audit(_ctx: PluginContext, **_: object) -> None:
return None


def plugin_manifest() -> PluginManifest:
return PluginManifest(
name="example-stigmem-plugin",
version="1.0.0",
requires_stigmem=">=0.9.0a1",
capabilities=frozenset(),
hooks={
"pre_assert_authorize": pre_assert_authorize,
"post_assert_audit": post_assert_audit,
},
)

Manifest fields:

Field
Required
Purpose
name
yes
Stable plugin name. Use lowercase letters, numbers, and hyphens.
version
yes
Plugin package version.
requires_stigmem
no
Compatibility line. Use >=0.9.0a1 unless your plugin requires a later alpha.
capabilities
no
Capability names the plugin may access through PluginContext.
hooks
no
Mapping from stable hook name to callable handler.
health_check
no
Lifecycle health callable used by operator inspection.
depends_on
no
Other plugin names that must register before this plugin.

Hook patternsโ€‹

Handlers always receive a PluginContext as the first positional argument. Hook-specific payloads are supplied as keyword arguments, except filter-chain hooks receive the current value as the second positional argument.

Semantic
Handler pattern
Return value
Voting
authorize / validate
Allow() or Deny("reason").
Filter chain
rewrite payload
The transformed value, never None.
Score delta
adjust ranking
dict[str, float] keyed by result or fact id.
Fire and forget
audit / observe
None.

Do not register hooks outside the 22-hook surface list.

health_check is a manifest lifecycle callable, not a hook name.

Capability declarationsโ€‹

PluginContext is capability gated. Declaring a capability in the manifest is required before a handler can ask the context for the corresponding core API handle.

from stigmem_node.plugins import PluginContext, PluginManifest


def post_assert_audit(ctx: PluginContext, **_: object) -> None:
audit = ctx.get_audit_emitter()
if callable(audit):
audit({"event_type": "example.plugin.post_assert"})


def plugin_manifest() -> PluginManifest:
return PluginManifest(
name="example-audit-plugin",
version="1.0.0",
requires_stigmem=">=0.9.0a1",
capabilities=frozenset({"audit.emit"}),
hooks={"post_assert_audit": post_assert_audit},
)

If a handler calls ctx.get_audit_emitter() without declaring audit.emit, registration can succeed but the handler will fail with a capability error when the hook fires.

The v0.9.0a1 CoreApis handles are deliberately narrow and optional. Operators or tests may expose a callable or facade object behind a capability; plugins should handle None or an unexpected shape explicitly.

Local testsโ€‹

Unit-test plugins without booting a node by registering the manifest in a HookRegistry:

from stigmem_node.plugins import Deny, HookRegistry

from example_stigmem_plugin import plugin_manifest


def test_blocks_configured_source() -> None:
registry = HookRegistry()
registry.register_plugin(plugin_manifest())

decision = registry.fire_voting(
"pre_assert_authorize",
source="agent:blocked",
)

assert isinstance(decision, Deny)
assert decision.reason == "source is blocked by example-stigmem-plugin"

For tests that need to replace the process-global registry temporarily:

from stigmem_node.plugins.testing import stigmem_plugins

from example_stigmem_plugin import plugin_manifest


def test_process_registry_fixture() -> None:
with stigmem_plugins([plugin_manifest()]) as registry:
assert "example-stigmem-plugin" in registry.registered_plugins()

Startup loadingโ€‹

After installing the plugin package, Stigmem discovers entry points from the stigmem.plugins group and registers them during startup. Dependency ordering is deterministic: dependencies listed in depends_on register before the dependent plugin, and cycles fail closed.

For local development only:

STIGMEM_PLUGIN_SIGNING_REQUIRED=false

That setting emits a security warning and audit metadata. Do not use it in production.

Signing and trust expectationsโ€‹

Production plugin registration requires a verified signing identity.

STIGMEM_PLUGIN_TRUSTED_PUBLISHERS

Lists accepted signing identities.

STIGMEM_PLUGIN_TRUST_OVERRIDE_PUBLISHERS

Lists explicit audited exceptions.

Unsigned plugins rejected

When STIGMEM_PLUGIN_SIGNING_REQUIRED=true.

Author checklistโ€‹

Before publishing a plugin:

  1. Use requires_stigmem=">=0.9.0a1" or a later alpha bound that matches your tested API surface.
  2. Register only stable hook names from the 22-hook surface.
  3. Declare only capabilities your handlers actually use.
  4. Add unit tests for allow, deny, and error paths.
  5. Verify startup discovery through the stigmem.plugins entry point.
  6. Coordinate signing identity and trusted-publisher configuration with the node operator.