Plugins & Tools
Tools are the agent's hands. Web search, code execution, file I/O, robot commands — each is a plugin that implements the same trait, discovered and dispatched through a central registry.
The plugin system is the primary extension point of the framework. Everything beyond the core reasoning loop is a plugin — tools, memory backends, sensors, even alternative agent cores.
The ToolPlugin trait
#[async_trait]pub trait ToolPlugin: Plugin { /// Execute the tool with the given arguments. async fn execute( &self, args: Value, ctx: &ToolContext, ) -> Result<Value, ToolError>; /// JSON Schema for the tool's input parameters. fn schema(&self) -> &ToolSchema;}
Every tool — whether it's a compiled WASM module, a JavaScript file, or native Rust code — implements this trait. The agent core and the registry only interact with tools through this interface.
Plugin manifest
Every plugin is described by a plugin.json manifest:
{ "name": "web.search", "version": "0.1.0", "type": "tool", "description": "Search the web using Brave Search or DuckDuckGo", "runtime": "wasm", "entrypoint": "search.wasm", "inputs": { "type": "object", "properties": { "query": { "type": "string", "description": "Search query" } }, "required": ["query"] }, "permissions": ["network:outbound"], "config": { "api_key": { "type": "string", "env": "BRAVE_API_KEY", "optional": true } }}
The manifest declares everything the framework needs to know about a plugin: what it does, how to run it, what permissions it needs, and how to configure it.
Plugin runtimes
Tools can execute in different runtimes. The Runtime Policy decides which one:
| Runtime | How it runs | Isolation | Best for |
|---|---|---|---|
wasm | WASM module via jan-wasm-worker subprocess | Process + WASM sandbox | Untrusted tools, community plugins |
js | JavaScript via WASM JS engine (Boa) | WASM sandbox | Quick scripting, user-created tools |
native | Compiled Rust, in-process | None | Core tools, performance-critical |
microvm | microsandbox microVM | Full VM isolation | Code execution, persistent state |
host | Direct host execution | None | Shell commands, robot control |
The same ToolPlugin trait works across all runtimes. The registry dispatches to the appropriate executor based on the manifest's runtime field and the active policy.
Plugin registry
The PluginRegistry discovers, loads, and manages all plugins:
let registry = PluginRegistry::new() // Register built-in tools .register(WebSearchTool::new()) .register(HttpFetchTool::new()) .register(CodeExecTool::new()) // Scan for user-installed plugins .scan_dir("~/.jan/plugins/").await?;
Discovery order
- Built-in — Compiled into the binary (existing WASM tools, shipped by default)
- User directory —
~/.jan/plugins/(user-installed plugins) - Project directory —
.jan/plugins/(project-specific overrides)
What the registry provides to the agent
// Agent core asks: "what tools are available?"let schemas = registry.tool_schemas();// → Sent to the LLM as the `tools` parameter// Agent core asks: "run this tool"let result = registry.dispatch("web.search", json!({"query": "rust async"})).await?;// → Registry finds the right plugin, checks policy, executes, returns result
Built-in tools (first citizens)
These are the existing Jan CLI tools, wrapped as plugins:
| Tool | Runtime | Description |
|---|---|---|
web.search | wasm | Brave Search with DuckDuckGo fallback |
http.fetch | wasm | GET any URL, 256 KB truncation |
code.exec | microvm | JavaScript, Python, Bash execution |
robot.move_forward | host | Robot movement commands |
robot.look | host | Robot camera/vision |
These are adapter-wrapped — the underlying execution is identical to today. The WASM tools still run via jan-wasm-worker subprocess. Robot tools still make HTTP calls. The plugin layer adds a uniform interface without changing how they work.
Creating a tool
WASM tool (Rust)
// Compiled to wasm32-wasip1#[no_mangle]pub extern "C" fn run(input_ptr: *const u8, input_len: usize) -> *const u8 { let input: Value = parse_input(input_ptr, input_len); let query = input["query"].as_str().unwrap(); let results = search(query); return_output(json!({ "results": results }))}
JavaScript tool
// ~/.jan/plugins/my-tool/tool.js// @tool my.tool// @description Does something useful// @schema {"type":"object","properties":{"input":{"type":"string"}},"required":["input"]}async function run(input) { return { result: input.input.toUpperCase() };}
Native tool (Rust)
pub struct MyTool;#[async_trait]impl ToolPlugin for MyTool { async fn execute(&self, args: Value, ctx: &ToolContext) -> Result<Value, ToolError> { let input = args["input"].as_str().unwrap(); Ok(json!({ "result": input.to_uppercase() })) } fn schema(&self) -> &ToolSchema { &MY_TOOL_SCHEMA }}
All three produce the same thing from the agent's perspective: a tool with a name, a schema, and an execute() method.
Skills — composed workflows
Skills are higher-level workflows built from tools. Instead of a single action, a skill runs multiple steps:
{ "name": "research.deep", "type": "skill", "steps": [ { "id": "search", "tool": "web.search", "input": { "query": "{{inputs.topic}}" } }, { "id": "read", "tool": "http.fetch", "loop": "{{steps.search.output.results[:3]}}" }, { "id": "summarize", "tool": "llm.generate", "input": { "prompt": "Summarize: {{steps.read.outputs}}" } } ]}
Skills reuse existing tools — no new code required. The framework's declarative skill engine handles execution, looping, and template interpolation.
The plugin system is designed so that community tools, internal tools, and built-in tools all use the same manifest format and the same trait. There is no "first-class" vs "third-party" distinction at the framework level.
Plugin configuration
Plugins declare their config schema in the manifest. Values resolve from (highest priority first):
- CLI flags —
--plugin-config web.search.api_key=xxx - Config file —
agent-config.json→plugins.web.search.api_key - Environment variables — declared in manifest:
"env": "BRAVE_API_KEY" - Defaults — declared in manifest
This means a plugin works out of the box with defaults, but can be customized at any level without changing code.