Skip to main content

Plugin interface v3

engine.plugin/v3 is the current public boundary between Engine and a world. A plugin may observe one or more targets and provide typed capabilities, controllers, executors, effect oracles, specialists, experience providers, and bounded lifecycle observers. It may also provide proposal-only autonomy strategies and typed goal templates. Engine remains responsible for the generic lifecycle, policy, authorization, and audit.

V3 extends the v2 action lifecycle. A v2 plugin with no autonomy roles remains loadable, but only a v3 plugin can be generically enrolled.

A plugin always has two sides:

  1. a static engine-plugin.toml, readable before import;
  2. a Python factory in the engine.plugins entry point group that returns an object with the WorldPluginV3 surface.

Import and factory invocation should be inert: do not connect, mutate a target, or start a background process. Connections begin only in an explicit provider or executor operation.

Registration

Declare the distribution entry point in pyproject.toml:

[project.entry-points."engine.plugins"]
my_world = "my_world.plugin:load_plugin"

The runtime locates the associated engine-plugin.toml, validates it, and compares the static manifest with plugin.manifest. A mismatch in identity, roles, capabilities, preferences, or routines blocks registration. Duplicate plugin and target IDs are also rejected.

Every loaded v3 provider, controller, executor, oracle, and specialist exposes a stable id plus the owning plugin_id. The loaded IDs must cover their static declaration exactly once. The same exact-coverage rule applies to observers, experience providers, routine compilers, autonomy strategies, and goal-template compilers.

Engine does not currently have a marketplace. Installation and distribution choices use normal Python packaging and remain under local operator control.

Minimal manifest

A mutating capability needs more information than a tool name:

[plugin]
id = "example.warehouse"
version = "0.1.0"
engine_api = ">=3.0,<4"
contract_version = "engine.plugin/v3"
description = "Bounded warehouse world"

[declarations]
world_providers = ["warehouse"]
controllers = ["warehouse-controller"]
executors = ["warehouse-executor"]
effect_oracles = ["warehouse-oracle"]
specialists = []
lifecycle_observers = []
entity_types = ["warehouse.bin"]
relation_types = []
observation_types = ["bin.count"]
experience_providers = []
routine_compilers = []

[autonomy]
strategies = []
goal_template_compilers = []

[needs]
network = []
filesystem = []
secrets = []
privacy = []

[store]
identity = "example.warehouse.store"
schema_version = 1

[[capability_families]]
id = "example.warehouse.transfer-bin/v1"
family = "warehouse.transfer-bin"
version = "1.0.0"
description = "Move a bounded number of crates"
control_layer = "semantic"
invocation_mode = "task"
risk_class = "low"
privacy_class = "local"
idempotent = true
deadline_ms = 5000
input_schema = {type = "object", required = ["from", "to", "count"]}
effect_schema = {type = "object", required = ["minimum_count"]}
effect_measurements = ["bin.count"]
limits = {count = {min = 1, max = 10}}
recovery = "poll_task_then_observe"
conflict_domain = "warehouse.inventory"

The manifest declares needs; it does not enforce those needs by itself. The current runtime does not yet provide general sandbox or permission enforcement based on [needs], and it does not cryptographically verify plugin artifacts. Treat signing and sandboxing as open product gaps, not existing safety guarantees.

Roles

WorldProvider

A provider owns a plugin_id, a stable target_id, polling and freshness intervals, and implements:

  • discover() for capability instances;
  • observe() for a monotonic TargetObservationV2;
  • subscribe(wake) as an optional wake-up source.

observe() returns entities, relations, observations, coverage, source, target revision, and availability. An event is only a reason to observe again; the event itself is not automatically operational truth.

DomainController

The controller translates a semantic ProposedActionV1 into an exact ActionRequestV1. This is where domain meaning, units, target parameters, target revision, deadline, and idempotency key are fixed. The controller may not change the target, entity, goal, or capability.

Executor

The executor receives only a concrete request plus an AuthorizationV1. It implements dispatch, poll, and cancel, and returns an ExecutionReceiptV2. A receipt states what the executor knows about execution; an acknowledgement does not prove that the intended effect exists in the world.

EffectOracle

The oracle compares the proposal, pre-snapshot, receipt, and a fresh post-snapshot. It produces an EffectDeltaV1 with evidence grade, achieved: true | false | null, measurements, and a reason. With insufficient coverage, null/UNKNOWN is more accurate than false.

SpecialistBrainV2

A specialist declares supported capability families and returns typed SpecialistAdviceV1. It may provide a proposal, but cannot authorize, dispatch, or establish its own success.

AutonomyStrategy

A v3 strategy receives only a bounded AutonomyContextV1 and returns one proposal-only AutonomyDecisionV1. It has no executor, policy, authorization, model, registry, or plugin handle and cannot start its own loop. Its only decisions are NOOP, DEFER, PROPOSE_EFFECT, PROPOSE_GOAL_CANDIDATE, REQUEST_EXECUTIVE, and REQUEST_SPECIALIST.

The static AutonomyStrategySpecV1 declares exact capability, template, context/privacy, cognition-route, and specialist needs. Static and runtime strategy declarations must match exactly.

GoalTemplateCompiler

A compiler turns a GoalCandidateV1 naming a declared GoalTemplateSpecV1 into inert GoalSpecV2 data. Heart checks the result against the enrollment and template, creates any exact mandate, and runs the normal action lifecycle. The compiler cannot authorize or dispatch, and the first release permits exactly one desired effect per compiled goal.

ExperienceProvider

An experience provider publishes cursor-based BehaviorBatchV1 values from plugin-owned storage. Engine stores signals exactly once per cursor and may link them to a namespaced preference or routine template. A behavior signal is evidence, not implicit permission.

RoutineCompiler

A routine compiler translates plugin-owned pattern semantics into an inert RoutineSpecV1 plus GoalSpecV2. It cannot create a mandate or authorization.

LifecycleObserver

A lifecycle observer receives a typed LifecycleEventV1 only after the corresponding Engine artifact or transition has been stored durably. It is intended for separately installed, explicitly declared outbound integrations such as bounded notifications. It cannot add facts, propose actions, authorize, dispatch, or serve as an effect oracle. Delivery is best-effort; failures are isolated and audited as lifecycle_observer_failed.

Because this role can cross a privacy boundary, its plugin manifest must declare the required network and privacy needs. Implementations should export only the smallest deterministic projection needed for their purpose. Lifecycle events are not a feed of raw observations: motion, light, sensor, snapshot, and individual behavior-signal changes must not be inferred or forwarded unless a separate, explicitly reviewed contract allows that data.

The reference engine.ntfy plugin is narrower still. It handles only GoalSpec creation, learning/routine candidate creation or promotion, RoutineSpec addition or activation, and a real model-backed ProposedAction. Its output is non-authoritative and never becomes observation or oracle evidence.

Discovery is bounded by the manifest

A provider may discover dynamic devices, but only previously declared capability families can enter the mutating path. An unknown family is projected as opaque, query, read_only, and observe_only. A newly discovered target device therefore cannot create new authority automatically.

For mutating capabilities, the manifest validator requires at least a provider, controller, executor, effect oracle, and in v3 a non-empty conflict_domain. A v1 plugin may remain visible through the compatibility bridge, but is observe-only in the v2 world runtime.

Mutation lifecycle

fresh observation
-> untrusted proposal
-> scope and schema validation
-> controller creates exact request
-> deterministic policy
-> request-bound authorization
-> executor dispatch/poll/cancel
-> fresh post-observation
-> plugin oracle reconciles effect
-> receipt and EffectDelta are stored durably

A capability with immediate can respond terminally at once. task uses a durable external handle, polling, deadline cancellation, and restart recovery. stream exists in the public contract and the store has scaffolding, but there is no end-to-end stream reference proving reconnect and cursor recovery yet.

Storage boundary

A plugin declares its own store identity and schema version. Plugin data does not belong in Engine's private operational tables, and Engine does not share a mutable database as an implicit interface. Exchange public contract values only.

What conformance does and does not prove

engine-plugin validate validates the static manifest. engine-plugin test runs the generated unittest suite. engine_sdk.check_plugin() checks identities, duplicate targets, provider observations, declarations, and undeclared families, among other structural properties.

V3 conformance also checks exact strategy/template/compiler roles at static and runtime boundaries. That proves contract shape and fake behavior. It does not prove network isolation, artifact signing, a physical safe state, timing guarantees, or certification.