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:
- a static
engine-plugin.toml, readable before import; - a Python factory in the
engine.pluginsentry point group that returns an object with theWorldPluginV3surface.
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 monotonicTargetObservationV2;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.