Files
plyngent/doc/plugins.md
T

5.9 KiB

Tool plugins

Third-party tools install as Python packages and register through the plyngent.tools entry-point group. The host allowlists which plugins load; default is load none.

Importing a plugin only registers tools into the process catalog. The CLI still selects which names become model-visible (surface=local today).

Layers (short)

DEFINE+REGISTER   @tool → ToolDefinition + catalog entry (source=plugin:…)
SELECT            catalog.select(surface=…, …) → list[ToolDefinition]
EXECUTE           ToolRegistry (confirm, instance/session bind, invoke)
Layer Question
Register What tools exist in this process?
Select Which may the model see this session?
Execute How are they confirmed and run?

Builtins use the same path with source=builtin. Plugins must not reuse a builtin (or another plugin) tool name — registration fails with a collision error.

Author a plugin package

1. Define tools with @tool

# acme_plyngent/tools.py
from plyngent.agent import ToolTag, tool


@tool(name="acme_ping", tags=ToolTag.LOCAL)
async def acme_ping() -> str:
    """Return a fixed pong (example plugin tool)."""
    return "pong"

Notes:

  • Prefer async def handlers (builtins are async-first).
  • Default tags=ToolTag.LOCAL. Set at least one of LOCAL or PUBLIC.
  • Workspace/exec tools should stay LOCAL unless reviewed for a shared host.
  • Session-only helpers may add PUBLIC and SESSION_STATE when they only touch session data and host-approved APIs (see plans under doc/plans/).
  • Optional soft-confirm bits: YOLO (eligible for YOLO auto-approve), TRUSTABLE (grant once per session after approve). Hard denylists are never YOLO-skipped.
  • Docstring becomes the model-facing description; parameter schemas come from type hints.
  • Use register=False only for unit tests that build a private registry.

2. Entry point

Call a zero-arg loader (or import a module) so @tool runs under the plugin registration source:

# acme_plyngent/__init__.py
def load() -> None:
    """Import tool modules so @tool registers into the process catalog."""
    from acme_plyngent import tools as _tools

    _ = _tools  # registration side effects

Declare the entry point (PEP 621 / setuptools / hatch style):

# pyproject.toml of the plugin package
[project]
name = "acme-plyngent"
version = "0.1.0"
dependencies = [
  "plyngent",  # or pin a compatible range
]

[project.entry-points."plyngent.tools"]
acme = "acme_plyngent:load"
  • Entry-point name (acme) is the plugin id used in config allowlists and in catalog metadata (plugin:acme).
  • Value is module:attr. If attr is callable, plyngent calls it after load; if it is a module, import alone is enough when tools registered at import time.

Install the plugin into the same environment as plyngent (editable fine):

pdm add acme-plyngent
# or: pip install -e ./path/to/acme-plyngent

Enable plugins in config

Plugins are not agent-only: allowlisting lives under a top-level [plugins] section so future non-tool extensions can reuse the same gate.

In the user config (plyngent config path):

[plugins]
# Allowlist of entry-point names. Default empty = load no plugins.
enable = ["acme"]
# enable = ["*"]          # every discovered plyngent.tools entry point
# Never load these names even if listed or matched by *:
# disable = ["legacy"]
Setting Meaning
enable = [] / omitted Load no plugins (safe default).
enable = ["acme", "other"] Load only those entry-point names.
enable = ["*"] Load all discovered plugins for the group.
disable = ["x"] Skip plugin id x even when allowlisted or *.

enable / disable are plugin entry-point names (e.g. acme), not individual tool function names.

Restart the chat REPL after changing config so the tool registry rebuilds.

CLI load order (simplified):

  1. register_builtin_tools()
  2. load_plugin_tools(plugins.enable, disable=plugins.disable)
  3. catalog.select(surface="local")ToolRegistry

Inspect the model surface in the REPL:

/tools --list

Listed tools show tags and catalog source (builtin vs plugin:…).

Policy checklist for authors

  1. Prefer LOCAL for host FS, shell, PTY, or arbitrary network side effects.
  2. Do not shadow builtin names (read_file, run_command, todo tools, …).
  3. Do not rely on process-global workspace/todo alone when a host binds instance/session context — prefer the same patterns as builtins (get_workspace_root already prefers instance policy; session state via context when you need it).
  4. Soft danger: annotate with YOLO / TRUSTABLE only when the product confirm story matches; hard denylists stay in workspace/command policy.
  5. Keep tools async; raise ordinary exceptions or return error: … strings consistently with builtins.

Surfaces (local vs public)

Catalog select Included tools
surface="local" (CLI) LOCAL and/or PUBLIC
surface="public" PUBLIC only

There is no multi-tenant public host in-tree yet; surface=public is available for hosts that want a PUBLIC-only select (builtins: mainly the todo series). Plugins should not set PUBLIC on workspace/exec tools by default.

API reference (code)

Piece Module
@tool, ToolTag, ToolRegistry plyngent.agent / plyngent.agent.tools
Catalog, ToolSource, select plyngent.tools.catalog
load_plugin_tools plyngent.tools.plugins
Config fields PluginsConfig.enable, PluginsConfig.disable ([plugins])