YAMS Plugin System¶
YAMS plugins extend the system with new capabilities. Choose your plugin type:
| Type | Use Case | Language | Deployment |
|---|---|---|---|
| External | Prototyping, integration, scripting | Python, Node.js, any | Process-based |
| Native | Production, performance-critical | C/C++ | Shared library |
External Plugins (Recommended for New Development)¶
External plugins run as separate processes communicating via JSON-RPC 2.0 over stdio.
Minimal Python Plugin¶
#!/usr/bin/env python3
"""Minimal YAMS external plugin."""
import json
import sys
def handle(req):
method = req.get("method", "")
if method == "handshake.manifest":
return {"name": "my_plugin", "version": "1.0.0", "interfaces": []}
elif method == "plugin.init":
return {"status": "ok"}
elif method == "plugin.health":
return {"status": "ok"}
elif method == "plugin.shutdown":
return {"status": "ok"}
else:
raise ValueError(f"Unknown method: {method}")
if __name__ == "__main__":
for line in sys.stdin:
req = json.loads(line.strip())
try:
result = handle(req)
resp = {"jsonrpc": "2.0", "id": req.get("id"), "result": result}
except Exception as e:
resp = {"jsonrpc": "2.0", "id": req.get("id"), "error": {"code": -32603, "message": str(e)}}
print(json.dumps(resp), flush=True)
Using the Python SDK¶
#!/usr/bin/env python3
from yams_sdk import BasePlugin, rpc
class MyPlugin(BasePlugin):
def manifest(self):
return {
"name": "my_plugin",
"version": "1.0.0",
"interfaces": ["content_extractor_v1"],
"capabilities": {
"content_extraction": {"formats": ["text/plain"]}
}
}
def init(self, config):
self.config = config
def health(self):
return {"status": "ok"}
@rpc("extractor.supports")
def supports(self, mime_type=None, extension=None):
return {"supported": extension == ".txt" or mime_type == "text/plain"}
@rpc("extractor.extract")
def extract(self, source, options=None):
with open(source["path"]) as f:
text = f.read()
return {"success": True, "content": text, "metadata": {}}
if __name__ == "__main__":
MyPlugin().run()
Required RPC Methods¶
Every external plugin MUST implement these JSON-RPC methods:
| Method | Purpose | Response |
|---|---|---|
handshake.manifest |
Return plugin metadata | {name, version, interfaces, capabilities} |
plugin.init |
Initialize with config | {status: "ok"} |
plugin.health |
Report health status | {status: "ok"\|"degraded"\|"error"} |
plugin.shutdown |
Graceful cleanup | null or {status: "ok"} |
Content Extractor Interface¶
Plugins implementing content_extractor_v1 must also provide:
| Method | Purpose | Response |
|---|---|---|
extractor.supports |
Check file type support | {supported: bool} |
extractor.extract |
Extract text content | {success: bool, content: str, metadata: {}} |
Plugin Directory Structure¶
For production deployment, organize plugins as directories:
my_plugin/
├── yams-plugin.json # Manifest (required)
├── plugin # Compiled binary (Linux/macOS)
├── plugin.exe # Compiled binary (Windows)
└── plugin.py # Python fallback
Plugin Manifest (yams-plugin.json)¶
{
"name": "my_plugin",
"version": "1.0.0",
"entry": {
"binary": {
"linux": "${plugin_dir}/plugin",
"darwin": "${plugin_dir}/plugin",
"windows": "${plugin_dir}/plugin.exe"
},
"fallback_cmd": ["python3", "-u", "${plugin_dir}/plugin.py"],
"env": {"LOG_LEVEL": "INFO"}
},
"capabilities": {
"content_extraction": {"formats": ["application/pdf"]}
}
}
Variable substitution: ${plugin_dir} expands to the plugin directory path.
Entry Point Resolution¶
The host loads plugins in this priority order:
entry.binary.<platform>- Platform-specific compiled binary (most secure)pluginorplugin.exe- Default compiled binary in plugin directoryentry.fallback_cmdorentry.cmd- Interpreter command- Direct execution based on file extension (
.py→ Python,.js→ Node.js)
Native Plugins (C ABI)¶
Native plugins are shared libraries loaded directly into the daemon process.
Required Entry Points¶
#include "yams/plugins/abi.h"
int yams_plugin_get_abi_version(void) { return 1; }
const char* yams_plugin_get_name(void) { return "my_plugin"; }
const char* yams_plugin_get_version(void) { return "1.0.0"; }
const char* yams_plugin_get_manifest_json(void);
int yams_plugin_get_interface(const char* iface_id, uint32_t version, void** out);
int yams_plugin_init(const char* config_json, const void* host_context);
void yams_plugin_shutdown(void);
int yams_plugin_get_health_json(char** out_json); // Optional
Interface Pattern¶
Plugins expose vtables for specific capabilities:
typedef struct content_extractor_v1 {
void* handle;
int (*extract_content)(void* handle, const char* path, char** out_text, char** out_metadata);
void (*free_extracted_content)(void* handle, char* text, char* metadata);
int (*get_supported_extensions)(void* handle, char*** out_exts, uint32_t* out_count);
void (*free_extension_list)(void* handle, char** exts, uint32_t count);
} content_extractor_v1_t;
Standard Interfaces¶
| Interface | Purpose |
|---|---|
content_extractor_v1 |
Document text extraction |
symbol_extractor_v1 |
Binary symbol extraction |
model_provider_v1 |
Embedding generation |
graph_adapter_v1 |
Graph storage backend |
search_provider_v1 |
Search backend |
Error Codes¶
#define YAMS_PLUGIN_OK 0
#define YAMS_PLUGIN_ERR_INVALID 1
#define YAMS_PLUGIN_ERR_NOT_FOUND 3
#define YAMS_PLUGIN_ERR_IO 4
Memory Management¶
- Plugin allocates with
malloc(), host frees withfree() - Vtables have static lifetime (valid until plugin unload)
- Thread-safe unless documented otherwise
Plugin Loading & Trust¶
Trust Policy¶
Default deny: Only explicitly trusted paths are loaded.
# Manage trust
yams plugin trust add /path/to/plugin
yams plugin trust list
yams plugin trust remove /path/to/plugin
# Development override
export YAMS_PLUGIN_TRUST_ALL=1
Trust file: ~/.config/yams/plugins_trust.txt
Search Paths¶
plugin_dirsfrom daemon config$YAMS_PLUGIN_DIRenvironment variable~/.local/lib/yams/plugins/usr/local/lib/yams/plugins
CLI Commands¶
yams plugin scan # Discover plugins
yams plugin list # Show loaded plugins
yams plugin load <path|name> # Load a plugin
yams plugin unload <name> # Unload a plugin
yams plugin info <name> # Show plugin details
Process Lifecycle (External Plugins)¶
States¶
Unstarted → Starting → Ready ⇄ Busy → ShuttingDown → Terminated
↓ ↓
Failed ←───────────────────────┘
Platform Implementation¶
| Platform | Spawn | Cleanup | Termination |
|---|---|---|---|
| Windows | CreateProcess |
Job Objects | TerminateProcess |
| Unix | fork/exec |
Process groups | SIGTERM → SIGKILL |
Automatic Recovery¶
Crashed plugins are restarted with exponential backoff (up to 3 retries by default).
JSON-RPC Protocol Reference¶
Request Format¶
{"jsonrpc": "2.0", "id": 1, "method": "method_name", "params": {...}}
Success Response¶
{"jsonrpc": "2.0", "id": 1, "result": {...}}
Error Response¶
{"jsonrpc": "2.0", "id": 1, "error": {"code": -32603, "message": "...", "data": {...}}}
Standard Error Codes¶
| Code | Name | Meaning |
|---|---|---|
| -32700 | Parse error | Invalid JSON |
| -32600 | Invalid Request | Missing required fields |
| -32601 | Method not found | Unknown method |
| -32602 | Invalid params | Bad parameters |
| -32603 | Internal error | Plugin error |
| -32000 | Extraction failed | Content extraction failed |
| -32001 | Unsupported format | Cannot handle file type |
Examples¶
Complete External Plugin Example¶
See: tests/fixtures/mock_plugin.py
Native Plugin Examples¶
plugins/onnx/- Model provider (ONNX Runtime)plugins/pdf_extractor/- Content extractor (MuPDF)plugins/symbol_extractor_treesitter/- Symbol extractor
External Plugin Examples¶
plugins/yams-ghidra-plugin/- Binary analysis (PyGhidra)
Reference¶
| Resource | Path |
|---|---|
| C ABI Specification | docs/spec/plugin_spec.md |
| JSON-RPC Protocol | docs/spec/external_plugin_jsonrpc_protocol.md |
| Interface Headers | include/yams/plugins/*.h |
| Python SDK | external/yams-sdk/ |
| Interface Registry | docs/spec/interface_versions.json |