mirror of
https://github.com/NCBM/plyngent.git
synced 2026-07-25 08:04:57 +08:00
179 lines
5.9 KiB
Markdown
179 lines
5.9 KiB
Markdown
# 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)
|
|
|
|
```text
|
|
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`
|
|
|
|
```python
|
|
# 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:
|
|
|
|
```python
|
|
# 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):
|
|
|
|
```toml
|
|
# 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):
|
|
|
|
```bash
|
|
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`):
|
|
|
|
```toml
|
|
[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:
|
|
|
|
```text
|
|
/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]`) |
|
|
|
|
## Related docs
|
|
|
|
- [architecture.md](./architecture.md) — package layout
|
|
- [plyngent.example.toml](./plyngent.example.toml) — config sample
|
|
- Design notes (may lag code): [plans/](./plans/) — registration, tags, PUBLIC surface
|