Plugins
Plugins are drop-in bundles that contribute hooks, skills, commands, agents, and MCP servers to an agent without modifying its code. A plugin lives in a directory with a .agentix-plugin/ subdirectory containing a plugin.json manifest.
Plugin directory layout
my-plugin/
└── .agentix-plugin/
├── plugin.json ← manifest (required)
├── hooks/
│ ├── audit.py ← each file must export register(hooks_dict)
│ └── metrics.py
├── skills/ ← SKILL.md files
├── commands/ ← *.md slash command definitions
├── agents/ ← *.md sub-agent definitions
└── .mcp.json ← MCP server configs { "mcpServers": { ... } }
plugin.json (manifest)
{
"name": "my-plugin",
"version": "1.0.0",
"description": "Audit logging and metrics for Agentix.",
"author": "Your Name",
"hooks": true,
"skills": true,
"commands": true,
"agents": true,
"mcp_servers": true,
"file_hashes": {
"hooks/audit.py": "a3f5c2...sha256hex...",
"hooks/metrics.py": "b7e1d9...sha256hex..."
}
}
Manifest fields
| Field | Type | Default | Description |
|---|---|---|---|
name | str | required | Unique plugin identifier |
version | str | "0.1.0" | Semver version string |
description | str | "" | Human-readable description |
author | str | dict | "" | Author name or {"name": ..., "email": ...} |
hooks | bool | list[str] | true | true = auto-discover all files in hooks/; list of filenames to load specific files only |
skills | bool | list[str] | true | true = auto-discover all skills |
commands | bool | list[str] | true | true = auto-discover all commands |
agents | bool | list[str] | true | true = auto-discover all agent definitions |
mcp_servers | bool | list[str] | true | true = load all servers from .mcp.json |
file_hashes | dict[str, str] | {} | SHA-256 hashes for integrity verification of hook files |
Hook file format
Each hook file must export a register(hooks_dict) function that adds HookMatcher instances to the provided dict:
# .agentix-plugin/hooks/audit.py
from agentix import HookMatcher
import logging
async def log_tool(hook_input: dict, context) -> None:
logging.info("AUDIT tool=%s input=%s", hook_input["tool_name"], hook_input.get("tool_input"))
def register(hooks: dict) -> None:
hooks.setdefault("PreToolUse", []).append(
HookMatcher(matcher=None, hooks=[log_tool])
)
Using a plugin
from agentix import AgentixAgentOptions
options = AgentixAgentOptions(
plugins=[
{"path": "./plugins/my-plugin"},
]
)
Multiple plugins are loaded in order; later plugins override earlier ones when asset names conflict.
Hash verification
Agentix verifies the SHA-256 digest of each hook file listed in file_hashes before loading it. If a file's content doesn't match, PluginIntegrityError is raised and the plugin is not loaded.
from agentix.plugin import PluginIntegrityError
try:
options = AgentixAgentOptions(plugins=[{"path": "./plugins/my-plugin"}])
except PluginIntegrityError as e:
print(f"Plugin tampered: {e}")
Generate hashes for a new plugin:
python -c "
import hashlib, pathlib
for f in pathlib.Path('.agentix-plugin/hooks').glob('*.py'):
h = hashlib.sha256(f.read_bytes()).hexdigest()
print(f' \"{f.relative_to(\".\")}\": \"{h}\"')
"
Trusted plugin paths
Skip hash verification for first-party or vendored plugins:
options = AgentixAgentOptions(
plugins=[{"path": "./plugins/my-plugin"}],
trusted_plugin_paths=["./plugins/internal-plugin"],
)
Discovering plugins programmatically
from agentix.plugin import discover_plugins, PluginManager
# Find all plugins in a directory
plugin_paths = discover_plugins(["./plugins", "~/.agentix/plugins"])
manager = PluginManager()
manager.load_from_paths(plugin_paths)
print(manager.get_status())