Skip to navigation

Plugin Architecture

View as Markdown

Every NeMo Helix capability, from agents and evaluation to guardrails, routing, and optimization, is a plugin: a Python package that registers its pieces through entry points. Skills are instructions, shipped with plugins, that teach coding agents how to use those capabilities.

Plugin Surfaces

A plugin contributes one or more surfaces. Each entry-point group maps a name to a class or function:

SurfaceEntry-point groupAdds
HTTP servicenemo.servicesFastAPI routers under /apis/<name>/
CLInemo.clinemo <name> ... commands
Jobnemo.jobsPlatform jobs, each with generated submit and explain commands
Controllernemo.controllersBackground reconciliation loops
Inference middlewarenemo.inference_middlewareMiddleware that virtual models can run
Python SDKnemo.sdkResources on the Python SDK client
Skillsnemo.skillsA directory of coding-agent skills
Studio UInemo.studioPages in NeMo Studio; see Plugin UIs

Some groups extend another plugin. For example, a job that declares an optimization strategy becomes a strategy of nemo agents optimize.

A minimal plugin depends only on nemo-helix-plugin:

[project]
name = "my-plugin"
version = "0.1.0"
dependencies = ["nemo-helix-plugin"]
[project.entry-points."nemo.services"]
my-plugin = "my_plugin.service:MyService"
[project.entry-points."nemo.cli"]
my-plugin = "my_plugin.cli:MyCLI"

Every HTTP route must declare its authorization, or the platform refuses to build its policy; see Plugin Authorization. For a complete plugin with every surface, see the example plugin.

Install a Plugin

The default plugins ship with nemo-helix; nemo --help lists the ones in your environment. Install another plugin into the same Python environment as the platform, then restart the platform so its entry points are discovered:

uv pip install -e plugins/prompt-master # from a source checkout
nemo services run

Check that it loaded:

nemo <plugin> --help # CLI commands
nemo agents optimize list-strategies # optimization strategies

Middleware plugins log Loaded inference middleware plugin '<name>' at startup.

Virtual Model Plugins

An inference middleware plugin processes requests and responses for any virtual model that lists it. Subclass NemoInferenceMiddleware and implement the hooks you need; unimplemented hooks pass through.

from typing import Any
from nemo_helix_plugin.inference_middleware import (
ImmediateResponse,
InferenceMiddlewareContext,
InferenceRequest,
NemoInferenceMiddleware,
)
from pydantic import BaseModel
class BlocklistConfig(BaseModel):
blocked_keywords: list[str] = []
class BlocklistMiddleware(NemoInferenceMiddleware):
async def validate_middleware_config(self, config_type: str, config: Any) -> BlocklistConfig:
return BlocklistConfig.model_validate(config)
async def process_request(
self,
ctx: InferenceMiddlewareContext,
request: InferenceRequest,
middleware_config: BlocklistConfig,
) -> InferenceRequest | ImmediateResponse:
text = " ".join(str(m.get("content", "")) for m in request.body.get("messages", [])).lower()
if any(word.lower() in text for word in middleware_config.blocked_keywords):
return ImmediateResponse(
data={"choices": [{"index": 0, "message": {"role": "assistant", "content": "Blocked."}}]}
)
return request
HookCalled
validate_middleware_configWhen a virtual model is created or updated; returns the config the request hooks receive.
get_middleware_configTo load a stored config named by config_id. Implement it when your plugin stores configs.
process_requestBefore the backend call. Edit request.body, headers, or path, rewrite request.body["model"] to route, or return an ImmediateResponse to answer without calling the backend.
process_responseAfter the backend call, on streaming and non-streaming responses.
on_startup, on_virtual_model_upserted, on_virtual_model_destroyedLifecycle events, for warming caches or validating models.

Register the class. The entry-point name is what virtual models reference:

[project.entry-points."nemo.inference_middleware"]
blocklist = "my_plugin.middleware:BlocklistMiddleware"
nemo inference virtual-models create safe-model \
--default-model-entity default/<model-entity> \
--request-middleware '[{"name": "blocklist", "config_type": "blocklist", "config": {"blocked_keywords": ["secret"]}}]'

nemo-switchyard and nemo-guardrails are built the same way. The example plugin’s middleware shows streaming redaction and stored configs.

Optimization Strategy Plugins

An optimization strategy is an ordinary job that declares one extra class variable, nemo_agent_optimization_strategy. The router behind nemo agents optimize run-strategy discovers every installed job that declares it, and forwards every submitted field except strategy to the job’s own spec.

from typing import ClassVar
from nemo_agent_optimization_plugin.schemas.strategies import OptimizationStrategy
from nemo_helix_plugin.job import NemoJob
from nemo_helix_plugin.job_context import JobContext
from pydantic import BaseModel
class MyStrategySpec(BaseModel):
agent: str
class MyStrategyJob(NemoJob):
name: ClassVar[str] = "my-strategy"
description: ClassVar[str] = "Build candidate configs for a platform agent."
spec_schema: ClassVar[type[BaseModel]] = MyStrategySpec
nemo_agent_optimization_strategy: ClassVar[OptimizationStrategy] = OptimizationStrategy(
name="my-strategy",
title="My strategy",
description="What this strategy changes, as shown by list-strategies.",
requires_config=False,
)
def run(self, config: dict, *, ctx: JobContext) -> dict:
spec = MyStrategySpec.model_validate(config)
candidates = ctx.storage.ephemeral / "candidates"
candidates.mkdir(parents=True, exist_ok=True)
... # fetch spec.agent, write one agent.yaml per candidate
return {"result": ctx.results.save("my-strategy", candidates).model_dump()}
[project.entry-points."nemo.jobs"]
"my-plugin.my-strategy" = "my_plugin.jobs:MyStrategyJob"

Users then run nemo agents optimize run-strategy --strategy my-strategy --agent my-agent. Fields outside the shared --agent, --optimize-config, --optimize-config-fileset, and --output flags travel through --spec. Keep the stored agent unchanged and write candidates to the job’s results.

To add a companion command to nemo agents optimize, register a register(group: typer.Typer) function under nemo.cli.agents.optimize. Prefix it with your strategy name unless it is generic.

The switchyard and prompt-master strategies are complete examples, including executor selection.

Skills

Skills are Markdown instructions (SKILL.md files) that teach a coding agent, such as Claude Code, Codex, Cursor, or OpenCode, to use NeMo Helix correctly. They are not runtime services. Each plugin ships the skills for its capability, so installed skills follow installed plugins.

nemo skills list # available skills
nemo skills show <skill> # print one skill
nemo skills install --agent claude # install all skills into this project
nemo skills install --agent claude --skill <skill>

nemo setup can install skills for you. To ship skills with a plugin, register a function that returns the directory holding one sub-directory per skill:

[project.entry-points."nemo.skills"]
my-plugin = "my_plugin.skills:skills_dir"

For skill structure and conventions, see the Skills Spec.