Plugin System#

The plugin system allows extending gptme with custom tools, hooks, and commands without modifying the core codebase.

Plugin Structure#

A plugin is a Python package (directory with __init__.py) that can contain:

my_plugin/
├── __init__.py          # Plugin metadata
├── tools/               # Tool modules (optional)
│   ├── __init__.py     # Makes tools/ a package
│   └── my_tool.py      # Individual tool modules
├── hooks/               # Hook modules (optional)
│   ├── __init__.py     # Makes hooks/ a package
│   └── my_hook.py      # Individual hook modules
└── commands/            # Command modules (optional)
    ├── __init__.py     # Makes commands/ a package
    └── my_command.py   # Individual command modules

Configuration#

Add plugin paths to your gptme.toml:

[plugins]
# Paths to search for plugins (supports ~ expansion and relative paths)
paths = [
    "~/.config/gptme/plugins",
    "~/.local/share/gptme/plugins",
]

# Optional: only enable specific plugins (empty = all discovered)
enabled = ["my_plugin", "another_plugin"]

Creating a Plugin#

1. Create Plugin Directory Structure#

mkdir -p ~/.config/gptme/plugins/my_plugin/tools
touch ~/.config/gptme/plugins/my_plugin/__init__.py
touch ~/.config/gptme/plugins/my_plugin/tools/__init__.py

2. Create a Tool Module#

my_plugin/tools/hello.py:

from gptme.tools.base import ToolSpec

def hello_world():
    """Say hello to the world."""
    print("Hello from my plugin!")
    return "Hello, World!"

# Tool specification that gptme will discover
hello_tool = ToolSpec(
    name="hello",
    desc="Say hello",
    instructions="Use this tool to greet the world.",
    functions=[hello_world],
)

3. Use Your Plugin#

Start gptme and your plugin tools will be automatically discovered and available:

$ gptme "use the hello tool"
> Using tool: hello
Hello from my plugin!

How It Works#

  1. Discovery: gptme searches configured plugin paths for directories with __init__.py

  2. Loading: For each plugin, gptme discovers:

    • Tool modules in tools/ subdirectory

    • Hook modules in hooks/ subdirectory

    • Command modules in commands/ subdirectory

  3. Integration:

    • Plugin tools are loaded using the same mechanism as built-in tools

    • Plugin hooks are registered during initialization via their register() functions

    • Plugin commands are registered during initialization via their register() functions

  4. Availability:

    • Tools appear in --tools list and can be used like built-in tools

    • Hooks are automatically triggered at appropriate lifecycle points

    • Commands can be invoked with / prefix like built-in commands

Plugin Tool Modules#

Plugins can provide tools in two ways:

Option 1: tools/ as a Package#

Create tools/__init__.py and gptme will import my_plugin.tools as a package:

# my_plugin/tools/__init__.py
from gptme.tools.base import ToolSpec

tool1 = ToolSpec(...)
tool2 = ToolSpec(...)

Option 2: Individual Tool Files#

Skip tools/__init__.py and create individual files:

my_plugin/tools/
├── tool1.py
└── tool2.py

Each file will be imported as my_plugin.tools.tool1, my_plugin.tools.tool2, etc.

Plugin Hook Modules#

Plugins can provide hooks to extend gptme’s behavior at various lifecycle points, similar to how tools work.

Option 1: hooks/ as a Package#

Create hooks/__init__.py and define a register() function:

# my_plugin/hooks/__init__.py
from gptme.hooks import HookType, register_hook
from gptme.message import Message

def my_session_hook(logdir, workspace, initial_msgs):
    """Hook called at session start."""
    yield Message("system", f"Plugin initialized in workspace: {workspace}")

def register():
    """Register all hooks from this module."""
    register_hook(
        "my_plugin.session_start",
        HookType.SESSION_START,
        my_session_hook,
        priority=0
    )

Option 2: Individual Hook Files#

Create individual hook modules without hooks/__init__.py:

# my_plugin/hooks/logging_hook.py
from gptme.hooks import HookType, register_hook
from gptme.message import Message

def log_tool_execution(log, workspace, tool_use):
    """Log tool executions."""
    print(f"Executing tool: {tool_use.tool}")
    yield  # Hooks must be generators

def register():
    """Register hooks from this module."""
    register_hook(
        "my_plugin.log_tool",
        HookType.TOOL_PRE_EXECUTE,
        log_tool_execution,
        priority=0
    )

Hook Types#

Available hook types:

  • SESSION_START - Called at session start

  • SESSION_END - Called at session end

  • TOOL_PRE_EXECUTE - Before tool execution

  • TOOL_POST_EXECUTE - After tool execution

  • FILE_PRE_SAVE - Before saving a file

  • FILE_POST_SAVE - After saving a file

  • GENERATION_PRE - Before generating response

  • GENERATION_POST - After generating response

  • And more (see gptme.hooks.HookType)

Hook Registration#

Every hook module must have a register() function that calls register_hook() for each hook it provides. The plugin system automatically calls register() during initialization.

Plugin Command Modules#

Plugins can provide custom commands that users can invoke with the / prefix, similar to built-in commands like /help or /exit.

Option 1: commands/ as a Package#

Create commands/__init__.py and define a register() function:

# my_plugin/commands/__init__.py
from gptme.commands import register_command, CommandContext
from gptme.message import Message

def weather_handler(ctx: CommandContext):
    """Handle the /weather command."""
    location = ctx.full_args or "Stockholm"
    # Your weather logic here
    yield Message("system", f"Weather in {location}: Sunny, 20°C")

def register():
    """Register all commands from this module."""
    register_command("weather", weather_handler, aliases=["w"])

Option 2: Individual Command Files#

Create individual command modules without commands/__init__.py:

# my_plugin/commands/joke.py
from gptme.commands import register_command, CommandContext
from gptme.message import Message

def joke_handler(ctx: CommandContext):
    """Tell a random joke."""
    jokes = [
        "Why did the AI cross the road? To optimize the other side!",
        "What's an AI's favorite snack? Microchips!",
    ]
    import random
    yield Message("system", random.choice(jokes))

def register():
    """Register command."""
    register_command("joke", joke_handler, aliases=["j"])

Using Plugin Commands#

Once registered, commands can be used like built-in commands:

$ gptme
> /weather London
Weather in London: Sunny, 20°C

> /joke
Why did the AI cross the road? To optimize the other side!

Command Handler Requirements#

Command handlers must:

  1. Accept a CommandContext parameter with:

    • args: List of space-separated arguments

    • full_args: Complete argument string

    • manager: LogManager instance

    • confirm: Confirmation function

  2. Be a generator (use yield) that yields Message objects

  3. Be registered via register_command() in a register() function

Example: Logging Plugin#

A complete example of a plugin that logs tool executions:

# my_logging_plugin/hooks/tool_logger.py
from gptme.hooks import HookType, register_hook
import logging

logger = logging.getLogger(__name__)

def log_tool_pre(log, workspace, tool_use):
    """Log before tool execution."""
    logger.info(f"Executing tool: {tool_use.tool} with args: {tool_use.args}")
    yield  # Hooks must be generators

def log_tool_post(log, workspace, tool_use, result):
    """Log after tool execution."""
    logger.info(f"Tool {tool_use.tool} completed")
    yield

def register():
    register_hook("tool_logger.pre", HookType.TOOL_PRE_EXECUTE, log_tool_pre)
    register_hook("tool_logger.post", HookType.TOOL_POST_EXECUTE, log_tool_post)

Example: Weather Plugin#

A complete example of a weather information plugin:

my_weather/tools/weather.py:

from gptme.tools.base import ToolSpec, ToolUse
import requests

def get_weather(location: str) -> str:
    """Get weather for a location."""
    # Implementation
    return f"Weather in {location}: Sunny, 72°F"

weather_tool = ToolSpec(
    name="weather",
    desc="Get current weather information",
    instructions="Use this tool to get weather for a location.",
    functions=[get_weather],
)

Configuration (~/.config/gptme/gptme.toml):

[plugins]
paths = ["~/.config/gptme/plugins"]

Usage:

$ gptme "what's the weather in San Francisco?"
> Using tool: weather
Weather in San Francisco: Sunny, 72°F

Distribution#

Plugins can be distributed as:

  1. Git repositories: Clone into plugin directory

    git clone https://github.com/user/gptme-plugin ~/.config/gptme/plugins/plugin-name
    
  2. PyPI packages: Install and add to plugin path

    pip install gptme-weather-plugin
    # Add site-packages location to plugins.paths in gptme.toml
    
  3. Local directories: Copy plugin folder to plugin path

    cp -r my_plugin ~/.config/gptme/plugins/
    

Migration from TOOL_MODULES#

The plugin system is compatible with the existing TOOL_MODULES environment variable.

Old approach:

export TOOL_MODULES="gptme.tools,my_custom_tools"
gptme

New approach (gptme.toml):

[plugins]
paths = ["~/.config/gptme/plugins"]
enabled = ["my_plugin"]

Both approaches work and can coexist. The plugin system provides better organization and discoverability for complex tool collections.

Future: Hooks and Commands#

Future phases will add support for:

  • Hooks: Plugin-provided hooks for events (e.g., pre-generation, post-execution)

  • Commands: Plugin-provided commands for the gptme CLI

Stay tuned for updates!

Troubleshooting#

Plugin not discovered:

  • Ensure plugin directory has __init__.py

  • Check plugin path is correctly configured in gptme.toml

  • Verify path is absolute or relative to config directory

Tools not loading:

  • Check tools/ directory exists and has proper structure

  • Verify tool modules define ToolSpec instances

  • Look for import errors in gptme logs

Plugin not enabled:

  • If using plugins.enabled allowlist, ensure plugin name is included

  • Remove enabled list to load all discovered plugins