Building a Plugin¶
A Raven plugin contributes to the runtime through a declarative manifest and Python factories. It is not a skill bundle, an MCP server, or a Codex plugin. Plugin code executes inside Raven's process and is trusted code, not sandboxed by the manifest.
Choose a contribution¶
Manifest group under plugin.contributes |
Purpose |
|---|---|
tools |
Model-callable actions |
hooks |
Loop timing hooks or supported participant factories |
memory_backends |
Memory storage/recall implementation |
services |
Resident-host lifecycle work |
tool_gates |
Plugin-specific tool adjudication |
session_observers |
Session lifecycle observations |
onboard |
Setup steps for the plugin's memory backend |
Use the existing contract in raven/contracts/ rather than importing CLI/RPC
internals. A Tool Gate is not the platform Permission Gate and does not replace
its authorization decisions.
Start with a working template¶
The default agent scaffold includes a directory-shaped plugin:
raven agents new example-agent --dry-run
Create it as described in Building an Agent, then customize the generated tool/hook. Its layout is:
plugins/example-agent-flow/
raven-plugin.toml
example_agent_flow/
__init__.py
plugin.py
tools/
__init__.py
hello.py
A manifest for that layout:
[plugin]
id = "example-agent-flow"
version = "0.1.0"
display_name = "Example Agent Flow"
enabled_by_default = true
[[plugin.contributes.tools]]
name = "example_agent_hello"
factory = "example_agent_flow.tools.hello:make_hello"
[[plugin.contributes.hooks]]
name = "example_agent_flow"
factory = "example_agent_flow.plugin:make_hook"
Directory discovery reads manifests without importing plugin code. Entry-point
discovery uses importlib.resources.files() to locate the packaged manifest,
which imports the package's __init__.py. Keep that module free of startup work.
Activation resolves the module.path:callable factories; a valid manifest alone
does not establish that a factory can load or its tool will register.
Factories and configuration¶
A factory receives PluginContext from raven.plugins.context:
configis only this plugin's admitted configuration slice.servicesexposes the declared host service surface.loggeris bound to the plugin identity.
Tool factories return a Tool or None to decline registration. A tool
provides a stable name, description, JSON-schema parameters, and asynchronous
execution returning text or ToolResult. Reuse the generated greeting example;
do not invent a different signature for a factory.
The config slice belongs under plugins.config["example-agent-flow"].
Declared config schema fields are checked at admission. An empty schema is
pass-through, not automatic validation of arbitrary values; validate your own
settings before using them. Unknown configuration keys may warn and pass through.
Prefix new tool names with the plugin/agent identity. A plugin tool using a
built-in name deliberately replaces that built-in; two plugins contributing
the same tool name conflict: the plugin activated first keeps the name and the
other is skipped. A plugin that fails to activate -- a factory that will not
import, or a name conflict -- is skipped alone: the host stays up, every other
plugin loads, the host prints a notice naming the plugin and its cause, and
raven plugins lists it as failed. So check the effective tool roster as well
as boot success. Adding its id to plugins.disabled stops the host loading it,
and the host's product sub-agents inherit that opt-out (a product never
inherits the opt-out of its own engine plugin).
Hooks and per-turn participants¶
The AgentHook timing phases are before_user_inbound, before_iteration,
before_execute_tools, after_iteration, terminal_answerless, and after_send.
The generated example overrides only after_send; other phases pass through.
Tool withholding belongs to before_iteration, not an arbitrary phase.
For agent-specific judgements, the current AgentParticipant paper offers
read-only StepView inputs and optional verbs such as intake, select_tools,
advise, system_addendum, review, salvage, outbound, and archive.
The host owns timing and adapts supported participant factories through the
hook contribution path. A participant is constructed per turn, so turn-local
state does not leak into another concurrent conversation.
Read raven/contracts/participant.py and raven/contracts/loop_hooks.py
before choosing. Preserve the contract's version/tier boundaries rather than
writing to the loop's transcript or reaching into its scheduler.
Lifecycle and packaging¶
Services implement the lifecycle in raven/contracts/services.py. Assembly
constructs contributions; the resident host starts and stops them. Do not start
background work as an import side effect or mutate the assembled runtime from
a service. Test shutdown and failure paths, not only startup.
Directory discovery scans <root>/<plugin-id>/raven-plugin.toml, including
roots supplied by plugins.dirs. Duplicate plugin ids resolve by the current
priority, from highest to lowest: bundled, user, project/extra directories,
then entry points.
Do not use a duplicate id as a version-selection mechanism without checking
which copy actually won.
For a separately installed engine, use the wheel-shaped scaffold. Its entry point targets a package, and its manifest must be packaged inside that package:
[project.entry-points."raven.plugins"]
example-agent-engine = "example_agent_engine"
Installing an enabled-by-default engine can activate discovery in every Raven
process in that environment. Keep the engine template's explicit configuration
gate so tools only register for the intended agent. Use uv for dependencies,
and keep runtime state and secrets outside the distributable package.
Verification checklist¶
- Parse the manifest through
PluginManifest.from_toml_path. - Import factories in the actual runtime environment and exercise enabled, disabled, and invalid-config cases.
- Test tool arguments/results and any denial or rollback path without a live model.
- Verify the effective roster and one harmless real task through the hosting surface that will use the plugin.
- Test two concurrent conversations, service shutdown, and a missing dependency.
Maintainer regression checks:
uv run pytest tests/test_plugin_manifest.py tests/test_plugin_registry.py tests/test_plugin_tools.py tests/test_plugin_hooks.py -q
Schemas are exported under schemas/. Developer workflow and repository gates
are in Development; permissions are documented separately in
Permissions and Security.