PluginRegistry

Discovers, manages, and exposes tool plugins. The registry acts as the central hub for all plugin operations, controlling which tools are available to the model.

from shared import PluginRegistry
Plugin Lifecycle
  1. Discover - Find available plugins
  2. Expose - Enable specific plugins
  3. Configure - Pass to JaatoClient
Quick example
from jaato import PluginRegistry

# Create registry with model name
registry = PluginRegistry(
    model_name="claude-sonnet-4-20250514"
)

# Discover available plugins
registry.discover()

# List what's available
print(registry.list_available())
# ['cli', 'mcp', 'file_edit', 'todo', ...]

# Expose specific plugins
registry.expose_tool("cli")
registry.expose_tool("file_edit")

# Check what's exposed
print(registry.list_exposed())
# ['cli', 'file_edit']

# Get tools for the model
schemas = registry.get_exposed_tool_schemas()
executors = registry.get_exposed_executors()

Constructor

__init__

PluginRegistry(model_name: Optional[str] = None)

Creates a new plugin registry. The model name is used to check plugin compatibility via model requirements.

  • model_name str optional
    Model name for checking plugin requirements. Some plugins only work with specific models.
Returns
PluginRegistry
Create registry
# Without model (no requirement checking)
registry = PluginRegistry()

# With model name
registry = PluginRegistry(
    model_name="claude-sonnet-4-20250514"
)

# Update model later
registry.set_model_name("gemini-2.5-flash")

Discovery

discover

discover(
  plugin_kind: str = "tool",
  include_directory: bool = True
) -> List[str]

Scans for available plugins and returns their names. Does not expose them automatically.

  • plugin_kind str optional
    Type of plugins to discover: "tool", "gc", or "model_provider"
    Default: "tool"
  • include_directory bool optional
    Include plugins from the plugins directory
    Default: True
Returns
List[str] - Names of discovered plugins

list_available

list_available() -> List[str]

Returns names of all discovered (available) plugins.

list_exposed

list_exposed() -> List[str]

Returns names of all currently exposed plugins.

is_exposed

is_exposed(name: str) -> bool

Checks if a plugin is currently exposed.

Discovering plugins
registry = PluginRegistry(model_name="gemini-2.5-flash")

# Discover tool plugins
tools = registry.discover(plugin_kind="tool")
print(f"Found {len(tools)} tool plugins:")
for name in tools:
    print(f"  - {name}")

# Discover GC plugins
gc_plugins = registry.discover(plugin_kind="gc")
print(f"Found {len(gc_plugins)} GC plugins")

# Check availability
available = registry.list_available()
print(f"Available: {available}")

# After exposing some
registry.expose_tool("cli")
print(f"Exposed: {registry.list_exposed()}")
print(f"CLI exposed: {registry.is_exposed('cli')}")

Exposure

Exposing a plugin makes its tools available to the model. You can expose plugins individually or all at once.

expose_tool

expose_tool(
  name: str,
  config: Optional[Dict] = None
) -> bool

Exposes a single plugin by name with optional configuration.

  • name str required
    Name of the plugin to expose
  • config Dict optional
    Plugin-specific configuration
Returns
bool - True if successfully exposed

unexpose_tool

unexpose_tool(name: str) -> None

Stops exposing a plugin's tools.

expose_all

expose_all(config: Optional[Dict[str, Dict]] = None) -> None

Exposes all discovered plugins, optionally with per-plugin config.

unexpose_all

unexpose_all() -> None

Stops exposing all plugins.

register_plugin

register_plugin(
  plugin: ToolPlugin,
  expose: bool = False,
  enrichment_only: bool = False,
  config: Optional[Dict] = None
) -> None

Manually registers a plugin instance (for custom plugins).

Exposing plugins
registry = PluginRegistry(model_name="gemini-2.5-flash")
registry.discover()

# Expose individual plugins
registry.expose_tool("cli")
registry.expose_tool("file_edit")

# Expose with configuration
registry.expose_tool("cli", config={
    "extra_paths": ["/usr/local/bin"],
    "max_output_chars": 10000
})

# Unexpose a plugin
registry.unexpose_tool("cli")

# Expose all at once
registry.expose_all()

# Expose all with per-plugin config
registry.expose_all(config={
    "cli": {"max_output_chars": 5000},
    "mcp": {"servers": ["Atlassian"]}
})

# Unexpose everything
registry.unexpose_all()
Register custom plugin
from my_plugins import CustomPlugin

# Create and register
plugin = CustomPlugin()
registry.register_plugin(
    plugin,
    expose=True,  # Expose immediately
    config={"api_key": "..."}
)

# Register for enrichment only
# (no tools, just prompt modification)
registry.register_plugin(
    enrichment_plugin,
    expose=False,
    enrichment_only=True
)

Retrieval

After exposing plugins, use these methods to get tool schemas and executors for the client.

get_exposed_tool_schemas

get_exposed_tool_schemas() -> List[ToolSchema]

Returns all tool schemas from exposed plugins.

get_exposed_executors

get_exposed_executors() -> Dict[str, Callable]

Returns a mapping of tool names to executor functions.

get_plugin

get_plugin(name: str) -> Optional[ToolPlugin]

Returns a plugin instance by name.

get_plugin_for_tool

get_plugin_for_tool(tool_name: str) -> Optional[ToolPlugin]

Returns the plugin that provides a specific tool.

get_system_instructions

get_system_instructions() -> Optional[str]

Returns combined system instructions from all exposed plugins.

get_auto_approved_tools

get_auto_approved_tools() -> List[str]

Returns list of tools that don't require permission approval.

get_exposed_user_commands

get_exposed_user_commands() -> List[UserCommand]

Returns user-facing commands from exposed plugins.

Getting tools and executors
registry.expose_tool("cli")
registry.expose_tool("file_edit")

# Get all tool schemas
schemas = registry.get_exposed_tool_schemas()
for schema in schemas:
    print(f"{schema.name}: {schema.description}")

# Get executors
executors = registry.get_exposed_executors()
print(f"Tools: {list(executors.keys())}")

# Get system instructions
instructions = registry.get_system_instructions()
if instructions:
    print(f"System: {instructions[:100]}...")

# Get auto-approved tools
auto_approved = registry.get_auto_approved_tools()
print(f"Auto-approved: {auto_approved}")
Plugin lookup
# Get plugin by name
cli_plugin = registry.get_plugin("cli")
if cli_plugin:
    print(f"CLI plugin: {cli_plugin.name}")

# Find which plugin provides a tool
plugin = registry.get_plugin_for_tool("execute_command")
if plugin:
    print(f"execute_command from: {plugin.name}")

# Get user commands
commands = registry.get_exposed_user_commands()
for cmd in commands:
    print(f"/{cmd.name}: {cmd.description}")

Enrichment Pipelines

The framework provides three enrichment pipelines that allow plugins to process content at different stages:

PipelineWhen CalledUse Cases
PromptBefore user message sentExpand @references
System InstructionAfter collecting instructionsExtract templates
Tool ResultAfter tool executionExtract from file reads

Prompt Enrichment

Plugins subscribe to modify user prompts before they're sent to the model.

enrich_prompt(prompt: str) -> PromptEnrichmentResult

System Instruction Enrichment

Plugins subscribe to modify combined system instructions (including MODULE.md content).

enrich_system_instructions(instructions: str) -> SystemInstructionEnrichmentResult

Tool Result Enrichment

Plugins subscribe to modify tool execution results before they're sent to the model.

enrich_tool_result(tool_name: str, result: str) -> ToolResultEnrichmentResult
Enrichment Priorities

Plugins are called in priority order (lower = earlier):

PriorityPluginPipelines
20referencesPrompt, Tool Result
40templateSystem Instructions, Tool Result
60multimodalPrompt
80memoryPrompt
90sessionPrompt
System instruction enrichment (template extraction)
class TemplatePlugin:
    name = "template"

    def get_system_instruction_enrichment_priority(self):
        return 40

    def subscribes_to_system_instruction_enrichment(self):
        return True

    def enrich_system_instructions(self, instructions):
        # Detect Jinja2 templates in MODULE.md content
        # Extract to .jaato/templates/
        return SystemInstructionEnrichmentResult(
            instructions=enriched,
            metadata={"extracted_count": 2}
        )

    # Also subscribe to tool result enrichment
    def subscribes_to_tool_result_enrichment(self):
        return True

    def enrich_tool_result(self, tool_name, result):
        # Same logic for file reads
        return ToolResultEnrichmentResult(
            result=enriched,
            metadata={"extracted_count": 1}
        )
Prompt enrichment (@reference expansion)
class ReferencesPlugin:
    name = "references"

    def get_enrichment_priority(self):
        return 20  # Run first

    def subscribes_to_prompt_enrichment(self):
        return True

    def enrich_prompt(self, prompt):
        # Detect @mod-code-015 mentions
        # Expand with reference content
        return PromptEnrichmentResult(
            prompt=expanded,
            metadata={"mentioned_references": ["mod-code-015"]}
        )

Model Requirements

Plugins can declare model requirements (glob patterns) to indicate which models they support. The registry tracks skipped plugins.

set_model_name

set_model_name(model_name: str) -> None

Updates the model name for requirement checking.

get_model_name

get_model_name() -> Optional[str]

Returns the current model name.

list_skipped_plugins

list_skipped_plugins() -> Dict[str, List[str]]

Returns plugins that were skipped due to model requirements, with their required patterns.

Model requirements
# Set model during creation
registry = PluginRegistry(model_name="gemini-2.5-flash")

# Or update later
registry.set_model_name("gemini-2.5-flash")

# Check current model
print(f"Model: {registry.get_model_name()}")

# After discovery, check skipped plugins
registry.discover()
skipped = registry.list_skipped_plugins()

for plugin, requirements in skipped.items():
    print(f"{plugin} requires: {requirements}")
Plugin with requirements
class GeminiOnlyPlugin:
    name = "gemini_only"

    def get_model_requirements(self):
        # Only works with Gemini models
        return ["gemini-*"]

    def get_tool_schemas(self):
        return [...]

# This plugin will be skipped if model
# doesn't match "gemini-*" pattern