Plugins
Three tiers of protocol extension. Manifest format, signing, hot-load lifecycle.
MAP's three-tier extensibility model lets the institution add new protocols without recompiling. Three tiers, ordered by performance and trust.
| Tier | Mechanism | Latency overhead | Trust level |
|---|---|---|---|
| Tier 1: Native | Compiled Rust crate implementing ProtocolModule | None | First-party (compiled into engine) |
| Tier 2: WASI | Wasmtime 37 component-model plugin loaded at runtime | Moderate (sandboxed) | Third-party with signature verification |
| Tier 3: External HTTP | Remote process implementing the MAP invoke contract | Higher (network hop) | Treaty-bound or local |
Tier 1 — Native
The reference path. A native protocol is a Rust crate in protocols/<name>-lib/ that implements:
#[async_trait]
impl ProtocolModule for MyProtocol {
fn protocol_name(&self) -> &'static str { "MYPROTOCOL" }
fn version(&self) -> &'static str { "v1.0.0" }
fn operations(&self) -> Vec<&'static str> { vec!["my_op"] }
async fn invoke(&self, op: &str, payload: Value, ctx: &InvokeContext)
-> Result<Response, ProtocolError> { /* ... */ }
}The crate is registered in the engine's static module table at startup. Latency is minimal — no IPC, no serialization across boundaries.
Use Tier 1 for first-party protocols that ship with the engine binary.
Tier 2 — WASI
A WASI plugin is a .wasm file built against the MAP plugin ABI. The engine loads it through Wasmtime 37 (component model + WASIP2). The plugin runs in a sandboxed memory space; it can only call the host functions exposed by the engine.
Manifest
Every WASI plugin ships with a plugin.toml:
[plugin]
name = "myorg-myprotocol"
version = "0.1.0"
description = "Custom analytics protocol for Acme"
plugin_type = "Protocol" # or "Gateway", "Extension"
protocol = "MYPROTOCOL" # which MAP protocol slot this implements
[plugin.binary]
source_type = "wasm"
path = "./myorg_myprotocol.wasm"
[plugin.metadata]
author = "did:oas:acme:agent:0x..."
license = "Commercial"
min_map_version = "0.6.0"
[plugin.operations]
operations = ["analyze", "summarize", "report"]
[plugin.integrations]
# Optional LLM / DB / vector store dependencies the plugin needs
llm = { kind = "openai-compat", endpoint = "https://api.openai.com/v1" }Loading
Plugins are discovered at startup by scanning a configured directory:
# map-engine config
[plugins]
directory = "/var/lib/map/plugins"
auto_discover = true
allowed_sources = ["wasm", "builtin"]
watch_interval_ms = 5000
require_signatures = true
trusted_publishers = [
"did:oas:l1fe:agent:0x...",
"did:oas:acme:agent:0x..."
]When auto_discover = true, the engine scans directory every watch_interval_ms for new manifests. New plugins enter PluginState::Pending, then Registered after the engine validates the manifest, verifies the signature, and successfully loads the WASM.
State machine
Pending → Registered → (Disabled | Error)| State | Meaning |
|---|---|
| Pending | Discovered; not yet loaded |
| Registered | Loaded; serving requests |
| Disabled | Deliberately turned off via admin command |
| Error | Load or runtime failure; reason recorded |
The engine exposes MarsAdmin::plugin_status(name) for ops queries.
Signing
If require_signatures = true, every plugin manifest must carry an Ed25519 signature from one of the trusted_publishers. The engine verifies the signature against the published OAS DID document before loading.
[plugin.signature]
signed_by = "did:oas:acme:agent:0x..."
signature = "ed25519:0x..."
signed_at = "2026-05-01T00:00:00Z"Unsigned or invalidly-signed manifests stay in Pending indefinitely and emit a PluginRejected audit event.
Building a WASI plugin
# In a Rust crate using plugin-sdk:
cargo build --release --target wasm32-wasip2
# Bundle for distribution
cp target/wasm32-wasip2/release/my_plugin.wasm dist/
cp plugin.toml dist/
mv dist my-plugin-v0.1.0/
tar czf my-plugin-v0.1.0.tar.gz my-plugin-v0.1.0/Deploy by dropping the unpacked plugin directory into /var/lib/map/plugins/.
Tier 3 — External HTTP
For any service implementing the MAP invoke contract over HTTP. Useful when the protocol is written in a language without a WASI target, or when the protocol calls heavyweight backends (large GPUs, proprietary engines).
The contract
#[async_trait]
pub trait RemoteProtocolAdapter: Send + Sync {
async fn invoke(&self, req: RemoteProtocolRequest)
-> Result<RemoteProtocolResponse, RemoteAdapterError>;
async fn health_check(&self) -> Result<RemoteHealthStatus, RemoteAdapterError>;
}The external service implements:
POST /invoke
GET /healthRequest:
POST /invoke HTTP/2
Content-Type: application/json
X-MAP-Protocol: MYPROTOCOL
X-MAP-Operation: analyze
X-MAP-Correlation-Id: ...
{
"operation": "analyze",
"payload": { ... },
"ctx": { /* serialized InvokeContext */ }
}Response (success):
HTTP/2 200 OK
Content-Type: application/json
{
"data": { ... },
"metadata": { ... }
}Response (error):
HTTP/2 422 Unprocessable Entity
{
"error": {
"code": "invalid_payload",
"message": "field 'subject' is required"
}
}Health:
GET /health → 200 OK
{
"status": "healthy",
"version": "1.0.0",
"uptime_seconds": 12345
}Registration
External adapters are registered explicitly in engine config:
[[external_protocols]]
name = "MYPROTOCOL"
version = "v1.0.0"
endpoint = "https://my-protocol.svc.cluster.local:8080"
timeout = "30s"
operations = ["analyze", "summarize"]
auth = { kind = "did-auth", did = "did:oas:l1fe:service:my-protocol-0x..." }The engine treats it like any other protocol — dispatches through the standard pipeline, audits to MAX, meters via MEAL.
Health & circuit breaker
Health probes run every 10s. Three consecutive failures open the circuit breaker for the endpoint; new requests fail fast with CoreError::CircuitOpen until probes recover.
Tier comparison
| Property | Tier 1 (Native) | Tier 2 (WASI) | Tier 3 (External) |
|---|---|---|---|
| Build artifact | Cargo crate | .wasm + manifest | Any HTTP service |
| Language | Rust only | Anything compiled to WASIP2 | Any |
| Latency overhead | None | ~50µs (sandbox call) | Network RTT |
| Sandbox | None (compile-time safety) | Wasmtime memory sandbox | Process/network isolation |
| Hot-load | Restart only | Yes (with watch_interval) | Yes (no restart needed) |
| Trust requirement | Compiled in | Signed manifest | DID-Auth or treaty |
| Audit through MAX | Yes | Yes | Yes |
| Meter through MEAL | Yes | Yes | Yes |
| State persistence | Engine's database | Engine-mediated KV | Service's own database |
Most third-party protocols start at Tier 3 (HTTP) for fast iteration, migrate to Tier 2 (WASI) once stable for tighter sandbox + lower latency, and may be absorbed to Tier 1 (native) if the L1fe core picks them up.
See also
- Engine plugin system
- Schemas — what the plugin's
inputSchemalooks like - Security — plugin signing and trust