FEATURES
๐ Plugin System: Custom Runtime Extensions
Extend aether with third-party runtimes via a JSON manifest and JSON-RPC protocol.
๐ Table of Contents¶
- Architecture Overview
- Plugin Manifest Format
- Plugin Discovery
- Register / Unregister Commands
- Plugin Protocol (JSON-RPC Style)
- Creating a Custom Runtime Plugin
- REST API Endpoints
- Persistent Registry
- Cross-References
๐๏ธ Architecture Overview¶
The aether plugin system enables any external binary to act as a runtime adapter. The architecture follows a straightforward pattern:
โโโโโโโโโโโโโโโโ JSON-RPC โโโโโโโโโโโโโโโโโโโโ
โ aether โ โโโโโโโโโโโโโโบ โ Plugin Binary โ
โ (CLI/API) โ โโโโโโโโโโโโโโ โ (any language) โ
โโโโโโโโโโโโโโโโ stdin/stdout โโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ~/.aether/plugins.json โ Persistent registry
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Design Summary¶
| Aspect | Decision |
|---|---|
| Discovery | Scan ~/.aether/plugins/ for *.json manifest files |
| Communication | JSON-RPC style messages over stdin/stdout |
| Registry | Persistent plugins.json keyed by plugin name |
| Capabilities | Each plugin declares which operations it supports |
| Language | Plugin binaries can be written in any language |
| Versioning | Semantic versioning in manifests; re-register to upgrade |
๐ Plugin Manifest Format¶
A plugin manifest is a JSON file that describes the plugin binary and its capabilities.
{
"name": "my-runtime",
"version": "0.1.0",
"runtime_kind": "custom-wasm",
"command": "/usr/local/bin/my-runtime",
"capabilities": ["build", "run", "stop", "status"]
}
Field Reference¶
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Human-readable plugin name. Used as the registry key -- must be unique. |
version |
string | Yes | Semantic version of the plugin (e.g. "1.2.3"). |
runtime_kind |
string | Yes | Custom runtime identifier this plugin provides. Used in compose files and CLI --runtime flags. |
command |
string | Yes | Absolute path to the plugin binary. |
capabilities |
array | Yes | List of supported operations: "build", "run", "stop", "status", "delete", "list". |
Capabilities Matrix¶
| Capability | Description | Required? |
|---|---|---|
build |
Build an image from a workload spec | Recommended |
run |
Run a workload instance | Required |
stop |
Stop a running instance | Required |
status |
Query instance status | Recommended |
delete |
Delete an instance permanently | Optional |
list |
List all managed instances | Optional |
Example Manifests¶
WASM Runtime:
{
"name": "wasm-plugin",
"version": "1.2.3",
"runtime_kind": "wasm",
"command": "/opt/wasm-runner",
"capabilities": ["run", "stop"]
}
Firecracker MicroVM:
{
"name": "firecracker",
"version": "2.0.0",
"runtime_kind": "microvm",
"command": "/usr/local/bin/fc-adapter",
"capabilities": ["build", "run", "stop", "status", "delete", "list"]
}
๐ Plugin Discovery¶
aether discovers plugins by scanning ~/.aether/plugins/ for *.json manifest files.
Directory Structure¶
~/.aether/
โโโ plugins/
โ โโโ wasm-runtime.json # โ
manifest for WASM plugin
โ โโโ firecracker.json # โ
manifest for Firecracker plugin
โ โโโ readme.txt # โ ignored (not .json)
โ โโโ bad-syntax.json # โ ๏ธ skipped with warning (invalid JSON)
โโโ plugins.json # ๐ persistent registry (auto-managed)
Discovery Rules¶
| Condition | Behavior |
|---|---|
.json extension |
File is loaded and parsed |
Non-.json file |
Silently skipped |
| Invalid JSON | Skipped with a warning log |
| Valid manifest | Merged into registry (keyed by name) |
| Directory missing | Returns 0 discovered (no error) |
| Duplicate names | Later discovery overwrites earlier entry |
Running Discovery¶
# Discover plugins from the default directory
aether plugin discover
# โ
Discovered 2 plugins (3 total registered)
โก Register / Unregister Commands¶
List Registered Plugins¶
aether plugin list
Displays a table of all registered plugins:
โญโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโฌโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ
โ Name โ Version โ Runtime Kind โ Command โ
โโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโผโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ wasm-runtime โ 1.0.0 โ wasm โ /usr/local/bin/wasm-runner โ
โ firecracker โ 0.3.0 โ microvm โ /opt/fc/fc-runtime โ
โฐโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโดโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ
Register a Plugin from a Manifest File¶
aether plugin register ./my-plugin.json
This reads the manifest file and adds it to the persistent registry. If a plugin with the same name already exists, it is overwritten with the new manifest (useful for upgrades).
Unregister a Plugin¶
aether plugin remove my-runtime
Removes the plugin from the registry by name. Returns the removed manifest if present, or a warning if the plugin was not found.
Discover Plugins¶
aether plugin discover
Scans ~/.aether/plugins/ and merges all valid manifests into the registry.
๐ก Plugin Protocol (JSON-RPC Style)¶
Plugins communicate with aether via a tagged JSON protocol over stdin/stdout. Each message includes a type field that identifies the request or response kind.
Request/Response Summary¶
| Request | Response | Fields |
|---|---|---|
BuildRequest |
BuildResponse |
spec_json โ image_json |
RunRequest |
RunResponse |
image_json + spec_json โ instance_json |
StopRequest |
StopResponse |
instance_json โ success (bool) |
StatusRequest |
StatusResponse |
instance_json โ status_json |
DeleteRequest |
DeleteResponse |
instance_json โ success (bool) |
ListRequest |
ListResponse |
(none) โ instances_json |
Request Message Examples¶
BuildRequest¶
{
"type": "BuildRequest",
"spec_json": "{\"name\": \"my-app\", \"image\": \"my-app:v1\"}"
}
RunRequest¶
{
"type": "RunRequest",
"image_json": "{\"tag\": \"v1\"}",
"spec_json": "{\"cpu\": \"2\", \"memory\": \"4Gi\"}"
}
StopRequest¶
{
"type": "StopRequest",
"instance_json": "{\"id\": \"inst-abc123\"}"
}
StatusRequest¶
{
"type": "StatusRequest",
"instance_json": "{\"id\": \"inst-abc123\"}"
}
DeleteRequest¶
{
"type": "DeleteRequest",
"instance_json": "{\"id\": \"inst-abc123\"}"
}
ListRequest¶
{
"type": "ListRequest"
}
Response Message Examples¶
BuildResponse¶
{
"type": "BuildResponse",
"image_json": "{\"name\": \"custom-img\", \"tag\": \"latest\"}"
}
RunResponse¶
{
"type": "RunResponse",
"instance_json": "{\"id\": \"inst-abc123\", \"name\": \"my-app\"}"
}
StopResponse / DeleteResponse¶
{
"type": "StopResponse",
"success": true
}
StatusResponse¶
{
"type": "StatusResponse",
"status_json": "{\"state\": \"running\", \"ready\": true}"
}
ListResponse¶
{
"type": "ListResponse",
"instances_json": "[{\"id\": \"inst-001\"}, {\"id\": \"inst-002\"}]"
}
Protocol Flow¶
aether โโstdinโโโบ {"type":"BuildRequest","spec_json":"{...}"}
plugin โโstdoutโโโบ {"type":"BuildResponse","image_json":"{...}"}
aether โโstdinโโโบ {"type":"RunRequest","image_json":"{...}","spec_json":"{...}"}
plugin โโstdoutโโโบ {"type":"RunResponse","instance_json":"{...}"}
Serialization Notes¶
- The
typefield is a tagged enum (serde#[serde(tag = "type")]) - Payload fields (
spec_json,instance_json, etc.) are JSON strings -- the plugin is responsible for parsing them - One message per line (newline-delimited JSON)
๐๏ธ Plugin Runtime Implementation¶
When a plugin is registered, Aether can use it as a full Runtime implementation via the PluginRuntime struct. This means plugins participate in the same lifecycle as built-in runtimes (Podman, Kubernetes, KubeVirt).
How It Works¶
- Binary validation: On creation,
PluginRuntimeverifies the plugin binary exists at the path specified in the manifest - Capability checking: Before each operation, the plugin's
capabilitieslist is checked -- callingbuildon a plugin that only supports["run", "stop"]returns an error - IPC call: A JSON-RPC message is written to the plugin's stdin, and the response is read from stdout
- Timeout: Each IPC call has a 60-second timeout -- plugins that hang are terminated with an error
- Error handling: Non-zero exit codes, invalid JSON responses, and unexpected message types all produce descriptive errors
Operation Flow¶
aether CLI
โ
โผ
PluginRuntime::build(spec)
โ 1. Check "build" in capabilities
โ 2. Serialize spec to JSON
โ 3. Spawn plugin binary
โ 4. Write {"type":"BuildRequest","spec_json":"..."} to stdin
โ 5. Read stdout (60s timeout)
โ 6. Parse {"type":"BuildResponse","image_json":"..."}
โ 7. Deserialize image from JSON
โผ
Image returned to caller
Error Scenarios¶
| Scenario | Error Message |
|---|---|
| Binary not found | Plugin binary '/path/to/bin' not found for plugin 'name' |
| Missing capability | Plugin 'name' does not support 'build' (capabilities: run, stop) |
| Timeout after 60s | Plugin 'name' timed out after 60s |
| Non-zero exit | Plugin 'name' exited with exit status 1: <stderr> |
| Invalid JSON response | Plugin 'name' returned invalid JSON: <parse error> (raw: ...) |
| Unexpected response type | Unexpected response from plugin: <type> |
Plugin Discovery in Runtime Factory¶
The create_plugin_runtime() function searches the plugin registry for a plugin whose runtime_kind matches the requested runtime name. This enables custom runtimes to be used anywhere a built-in runtime is accepted:
# Use a plugin-provided runtime in compose files
# runtime: wasm โ matches a plugin with runtime_kind: "wasm"
aether compose up
Log streaming¶
Plugins with the "logs" capability in their manifest receive LogsRequest IPC messages and return LogsResponse with log text. The CLI and dashboard call PluginRuntime::logs() when the capability is present.
{
"capabilities": ["build", "run", "stop", "status", "delete", "list", "logs"]
}
If "logs" is omitted, the runtime returns a friendly message that log streaming is not supported for that plugin.
๐ ๏ธ Creating a Custom Runtime Plugin¶
This walkthrough creates a minimal plugin in Bash. Plugins can be written in any language (Rust, Go, Python, Node.js, etc.).
Step 1: Create the Plugin Binary¶
#!/usr/bin/env bash
# File: /usr/local/bin/my-custom-runtime
set -euo pipefail
while IFS= read -r line; do
type=$(echo "$line" | jq -r '.type')
case "$type" in
BuildRequest)
echo '{"type":"BuildResponse","image_json":"{\"name\":\"custom-img\",\"tag\":\"latest\"}"}'
;;
RunRequest)
ID="inst-$(date +%s)"
echo "{\"type\":\"RunResponse\",\"instance_json\":\"{\\\"id\\\":\\\"$ID\\\"}\"}"
;;
StopRequest)
echo '{"type":"StopResponse","success":true}'
;;
StatusRequest)
echo '{"type":"StatusResponse","status_json":"{\"state\":\"running\",\"ready\":true}"}'
;;
DeleteRequest)
echo '{"type":"DeleteResponse","success":true}'
;;
ListRequest)
echo '{"type":"ListResponse","instances_json":"[]"}'
;;
esac
done
chmod +x /usr/local/bin/my-custom-runtime
Step 2: Create the Manifest¶
Save to ~/.aether/plugins/my-custom-runtime.json:
{
"name": "my-custom-runtime",
"version": "0.1.0",
"runtime_kind": "custom",
"command": "/usr/local/bin/my-custom-runtime",
"capabilities": ["build", "run", "stop", "status", "delete", "list"]
}
Step 3: Discover and Verify¶
# Create the plugins directory if needed
mkdir -p ~/.aether/plugins
# Discover the plugin
aether plugin discover
# โ
Discovered 1 plugin
# Verify it's registered
aether plugin list
# โญโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโฌโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ
# โ Name โ Version โ Runtime Kind โ Command โ
# โโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโผโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
# โ my-custom-runtime โ 0.1.0 โ custom โ /usr/local/bin/my-custom-runtime โ
# โฐโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโดโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ
Step 4: Use in a Compose File¶
version: "1"
workloads:
my-app:
spec: ./app.yaml
runtime: custom # matches runtime_kind from the manifest
Step 5: Upgrade the Plugin¶
To upgrade, update the manifest file and re-discover:
# Update version in the manifest
# Then:
aether plugin discover
# or:
aether plugin register ~/.aether/plugins/my-custom-runtime.json
๐ REST API Endpoints¶
GET /api/plugins -- List Registered Plugins¶
curl http://localhost:5090/api/plugins
Response:
{
"success": true,
"data": [
{
"name": "wasm-runtime",
"version": "1.0.0",
"runtime_kind": "wasm",
"command": "/usr/local/bin/wasm-runner",
"capabilities": ["run", "stop"]
}
],
"error": null
}
POST /api/plugins/discover -- Trigger Plugin Discovery¶
curl -X POST http://localhost:5090/api/plugins/discover
Response:
{
"success": true,
"data": {
"discovered": 2,
"total": 3
},
"error": null
}
๐พ Persistent Registry¶
The plugin registry is stored at ~/.aether/plugins.json. It persists across CLI invocations and API server restarts.
Registry Structure¶
{
"plugins": {
"wasm-runtime": {
"name": "wasm-runtime",
"version": "1.0.0",
"runtime_kind": "wasm",
"command": "/usr/local/bin/wasm-runner",
"capabilities": ["run", "stop"]
},
"firecracker": {
"name": "firecracker",
"version": "2.0.0",
"runtime_kind": "microvm",
"command": "/usr/local/bin/fc-adapter",
"capabilities": ["build", "run", "stop", "status", "delete", "list"]
}
}
}
Registry Behaviors¶
| Operation | Effect |
|---|---|
discover |
Scans directory, merges manifests, saves registry |
register |
Reads manifest file, inserts/updates entry, saves |
remove |
Deletes entry by name, saves |
| Load from missing file | Returns empty registry (no error) |
| Duplicate name on register | Overwrites existing entry |
๐ Cross-References¶
| Document | Relevance |
|---|---|
| Compose Guide | Use plugins as runtime targets in compose files |
| Security Guide | Policy enforcement for plugin-deployed workloads |
| API Reference | Full REST API documentation |
| Quick Reference | Command cheat sheet |