Plugins
Plugins extend Squadron with additional tools. They run as separate processes, communicate over gRPC, and can be written in Go or Python. Squadron manages installation, lifecycle, and shutdown — you just declare the plugin in HCL and use its tools.
Loading Plugins
plugin "playwright" {
source = "github.com/mlund01/plugin_playwright"
version = "v0.0.2"
settings {
headless = false
browser_type = "chromium"
}
}| Attribute | Type | Description |
|---|---|---|
source | string | Plugin source — github.com/owner/repo for a published release, or a path inside the project for a local Go or Python package |
version | string | Release tag, or "local" for local development |
settings | block | Plugin-specific configuration; passed to the plugin’s Configure (optional) |
On first load Squadron downloads the matching release asset from GitHub,
verifies its sha256 against checksums.txt, and caches the result.
Subsequent loads reuse the cache. See
Distributing Plugins for the
publishing side.
Local Plugin Sources
Point source at a local Go or Python package and Squadron will
auto-rebuild on every config load — no manual squadron plugin build
step in the dev loop:
plugin "shell" {
source = "./plugin_shell" # path inside the project
version = "local"
}Rules:
versionmust be"local"whensourceis a local path.- The path is resolved against the current working directory and must
stay inside the project root —
../../somewhere/elseis rejected at config-load time. - The source directory must contain a
go.mod(Go plugin) or apyproject.toml(Python plugin); Squadron auto-detects which. - For Go:
gomust be onPATH. For Python:python3must be onPATH. Build failures surface in the config-load error with the build tool’s output attached. - The compiled binary or installed venv lands at the same cache
location as a downloaded release:
.squadron/plugins/<platform>/<name>/local/.
Build caching
Squadron hashes the source tree after each successful build and stamps
the digest into runner.json (source_hash). On the next config load,
if the tree still hashes the same and the entry binary is on disk, the
rebuild is skipped — even for Python plugins where pip install would
otherwise dominate startup. Editing any file triggers a fresh build.
The hash walk ignores VCS metadata, virtualenvs, caches, and compiled
artifacts (.git/, __pycache__/, .venv/, node_modules/,
*.pyc, etc.) so incidental changes don’t invalidate the cache.
Using Plugin Tools
Once loaded, plugin tools are available as plugins.<name>.<tool>:
agent "browser" {
tools = [plugins.playwright.browser_navigate]
}
agent "browser" {
tools = [plugins.playwright.all]
}Plugin Paths
Plugins are cached at .squadron/plugins/<platform>/<name>/<version>/.
Each install carries a runner.json that records how to spawn the
plugin:
.squadron/plugins/darwin-arm64/playwright/v0.0.2/
├── runner.json # { "kind": "go", "entry": "plugin" }
└── plugin # the Go binary.squadron/plugins/darwin-arm64/myplug/v0.1.0/
├── runner.json # { "kind": "python", "entry": "venv/bin/myplug" }
└── venv/ # virtualenv with the plugin package installedIf runner.json is missing, Squadron falls back to executing a
plugin binary in the install directory (the legacy convention).
Creating Plugins
Plugins implement four methods: Configure, Call, GetToolInfo,
ListTools. Both SDKs ship a high-level decorator/generic API on top
that handles schema reflection, payload validation, and result
serialization for you — you just write a function.
Go
Use squadron-sdk :
package main
import (
"context"
"strings"
squadron "github.com/mlund01/squadron-sdk"
)
type EchoInput struct {
Message string `json:"message" jsonschema:"required,description=The message"`
AllCaps bool `json:"all_caps,omitempty"`
}
func main() {
app := squadron.New()
app.Configure(func(settings map[string]string) error {
return nil
})
squadron.Tool(app, "echo", "Echoes a message back",
func(ctx context.Context, in EchoInput) (string, error) {
if in.AllCaps {
return strings.ToUpper(in.Message), nil
}
return in.Message, nil
})
app.Serve()
}Schema is reflected from EchoInput’s jsonschema: tags. Build and
install for local use:
squadron plugin build myplug ./path/to/sourceThe build command runs go build from the source directory and writes
runner.json.
Python
Use squadron-sdk-py :
from typing import Literal
from pydantic import Field
from squadron_sdk import Squadron
app = Squadron()
@app.configure
def setup(settings: dict[str, str]) -> None:
app.prefix = settings.get("prefix", "")
@app.tool
def echo(
message: str = Field(..., description="The message to echo"),
all_caps: bool = False,
) -> str:
"""Echo a message back."""
out = message.upper() if all_caps else message
return f"{app.prefix}{out}"
def main():
app.serve()Tool schemas (input and output) are reflected from your type hints by
pydantic; @app.configure receives settings just like Go’s
app.Configure. The plugin needs a standard pyproject.toml with one
[project.scripts] entry that points at this main:
[project]
name = "myplug"
version = "0.1.0"
dependencies = ["squadron-sdk>=0.1.1"]
[project.scripts]
myplug = "myplug.main:main"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"Build and install for local use:
squadron plugin build myplug ./path/to/sourceThe build command detects pyproject.toml, creates a virtualenv at
.squadron/plugins/.../venv/, runs pip install <source>, and writes
runner.json pointing at the entry script.
Splitting tools across files, including a ToolGroup from another
module, and reading app state from group tools are documented in the
squadron-sdk-py README .
Local Development
Two ways to iterate on a plugin:
Declarative — let HCL drive the rebuild. Point source at the
plugin’s source directory and Squadron will rebuild on every config
load:
plugin "myplug" {
source = "./path/to/source"
version = "local"
}Imperative — build once, reference by name. Run the CLI yourself,
then reference the plugin without a source:
squadron plugin build myplug ./path/to/sourceplugin "myplug" {
version = "local"
}CLI Helpers
squadron plugin tools <name> # list tools the plugin provides
squadron plugin info <name> <tool> # show input + output schemas
squadron plugin call <name> <tool> '<json>' # invoke directly (useful for testing)
squadron plugin build <name> <source-path> # build + install (Go or Python)