Plugins
A team-root plugin.ts extends the runtime with code — custom tools, and
the executor actions that back gated ones. Every agent in the tree inherits
it, though each agent’s own tools.json still gates who can actually call
what it exposes.
import { definePlugin } from '@runravel/ravel';import { z } from 'zod';
export default definePlugin({ name: 'scribe', env: ['NOTES_API_KEY'], tools: [ { name: 'note_append', description: 'Append a line to the team scratchpad.', schema: z.object({ text: z.string() }), handler: async (input, ctx) => { await ctx.memory.append('team', 'scratchpad', input.text); return { ok: true }; }, }, { name: 'publish_note', description: 'Publish the scratchpad externally. Irreversible.', schema: z.object({ noteId: z.string() }), handler: async () => ({ status: 'proposed' }), }, ], actions: { publish_note: async (input, ctx) => { // the real, approved side effect — runs deterministically once a // human approves the "publish_note" proposal, no extra model call. }, },});Tools vs. actions
Section titled “Tools vs. actions”toolsbecome an in-process MCP server (namedplugin) that agents can call, subject to the policy set in their owntools.json.actionsregister on the executor. A tool withpolicy: "ask"must have a same-named action — otherwise an approved proposal has nothing to actually run.
Every tool handler receives a context object:
| Field | What it gives you |
|---|---|
memory |
Read/write access to agent, team, and org memory scopes |
teamScope |
The team this plugin is running for |
nodeId |
The calling agent’s path in the org tree |
cwd |
The team’s working directory |
env |
Resolved values for the env vars this plugin declared |
Declare the environment variable names your plugin’s tools need in the
env array on definePlugin. The runtime resolves them from the calling
agent’s .env chain and makes them available on ctx.env — the plugin
never has to reach into process.env directly.
Reload behavior
Section titled “Reload behavior”A plugin loads once per ravel serve process, inherited by the nearest
ancestor’s declaration down the tree. Changing plugin code requires
restarting the server — ravel watch’s hot-reload covers agent.md /
tools.json / processes, not plugin code.