Python SDK for writing Squadron tool plugins (wire-compatible with the Go squadron-sdk)
Project description
squadron-sdk (Python)
Python SDK for writing Squadron tool
plugins. Wire-compatible with the Go
squadron-sdk: a host built against
either SDK can launch plugins built against either SDK, in either language.
Built on pyplugin, the byte-for-byte Python port of HashiCorp's go-plugin (including AutoMTLS with ECDSA P-521).
Quick start
# plugin.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
async def echo(
message: str = Field(..., description="Text to echo back."),
repeat: int = Field(1, ge=1, le=100),
) -> dict:
"""Echo a message back, prefixed with the configured prefix."""
return {"echo": (app.prefix + message) * repeat}
@app.tool
def reverse(s: str, mode: Literal["chars", "words"] = "chars") -> str:
"""Reverse a string by characters or words."""
return " ".join(reversed(s.split())) if mode == "words" else s[::-1]
if __name__ == "__main__":
app.serve()
That's the whole plugin. The host gets:
- a
ToolPlugin.ListToolsresponse withechoandreverse, - a JSON Schema derived from your type hints (including
Field(...)metadata,Literalenums, defaults, validators, nested pydantic models, …), - input validation on every
Call, - automatic JSON serialization of return values.
Sync and async tool functions both work. Tool name defaults to the function
name and the description defaults to the docstring; override either with
@app.tool(name="...", description="...").
Typed returns
The return type annotation is reflected into a JSON Schema and shipped as
the tool's output_schema — same machinery as the input. Plain str
returns pass through unwrapped (the LLM sees hello rather than
"hello"); everything else is JSON-marshaled via pydantic, so BaseModel,
dataclasses, list[T], dict[K, V], Literal, etc. all work.
class Item(BaseModel):
name: str
count: int
@app.tool
def make_item(name: str) -> Item:
return Item(name=name, count=3)
# wire: {"name":"x","count":3}
# output_schema: {"type":"object","properties":{"name":{"type":"string"},"count":{"type":"integer"}},"required":["name","count"]}
@app.tool
def upper(s: str) -> str:
return s.upper()
# wire: HI
# output_schema: {"type":"string"}
The output schema flows over the wire and is available to LLM SDKs that support per-tool output schemas — symmetric with the input schema.
What gets generated
For the echo tool above, the schema sent to the host looks like:
{
"type": "object",
"properties": {
"message": {"type": "string", "description": "Text to echo back."},
"repeat": {"type": "integer", "default": 1, "maximum": 100, "minimum": 1}
},
"required": ["message"]
}
Nested pydantic models, Literal[...], list[T], dict[K, V], Annotated,
optional fields with defaults — all the usual pydantic conveniences are
available because we go through pydantic.create_model and ship the
schema verbatim.
Calling from a Python host
import asyncio, sys
from pyplugin import Client, ClientConfig
from squadron_sdk import HANDSHAKE, PLUGIN_KEY, ToolPlugin
async def main():
async with Client(ClientConfig(
handshake_config=HANDSHAKE,
plugins={PLUGIN_KEY: ToolPlugin()},
cmd=[sys.executable, "plugin.py"],
)) as client:
tool = client.dispense(PLUGIN_KEY)
await tool.configure({"prefix": "hi: "})
for info in await tool.list_tools():
print(info.name, info.description)
print(await tool.call("echo", '{"message":"world"}'))
asyncio.run(main())
A complete runnable example lives in examples/echo/.
Splitting tools across files
Two patterns work — pick whichever fits.
Shared app instance
A standalone Python app that owns its own tools: just import the same app
everywhere and decorate as you go.
# myplugin/app.py
from squadron_sdk import Squadron
app = Squadron()
# myplugin/tools/database.py
from myplugin.app import app
@app.tool
async def query(sql: str) -> dict: ...
# myplugin/main.py
from myplugin.app import app
from myplugin.tools import database # registration happens at import time
if __name__ == "__main__":
app.serve()
Explicit ToolGroup
Better when tools are a reusable unit (a library, a swappable bundle, or
just clearly-bounded functionality). Tools in a group can read app-level
state via group.app, which is set when you include the group:
# myplugin/tools/text.py
from squadron_sdk import ToolGroup
text_tools = ToolGroup()
@text_tools.tool
def shout(s: str) -> str:
return text_tools.app.prefix + s.upper()
# myplugin/main.py
from squadron_sdk import Squadron
from myplugin.tools.text import text_tools
app = Squadron()
@app.configure
def setup(settings):
app.prefix = settings.get("prefix", "")
app.include(text_tools) # text_tools.app is now `app`
app.include(text_tools, prefix="t_") # or namespace: t_shout
app.serve()
ToolGroup is just a tool registry — same @tool decorator, no
@configure or .serve(). Tool collisions raise on registration or
include. A group can only be included into one app.
Low-level API
If you need fully dynamic tools (e.g. discovered at runtime from a remote
schema), implement ToolProvider directly
and call serve(provider). Squadron is a thin layer over ToolProvider
that handles the registration plumbing.
Wire compatibility
Same handshake (SQUAD_PLUGIN / squadron-tool-plugin-v1, protocol
version 1) and protobuf service (plugin.ToolPlugin) as the Go SDK. A Go
Squadron host can launch a Python plugin built with this package, and a
Python host built with pyplugin can launch a Go plugin built with the Go
SDK.
The proto file lives at
src/squadron_sdk/proto/plugin.proto
and is identical to the Go SDK's. Regenerate the stubs with:
python scripts/gen_protos.py
Development
python -m venv .venv
source .venv/bin/activate
pip install -e '.[dev]'
pytest
License
MIT.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file squadron_sdk-0.1.1.tar.gz.
File metadata
- Download URL: squadron_sdk-0.1.1.tar.gz
- Upload date:
- Size: 13.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6c51fcd52d30ba0036234c23408da5588c5500e62e32138551b478045c8a74e5
|
|
| MD5 |
e300ca23467df99ea2de1bad251ddbe8
|
|
| BLAKE2b-256 |
c5cd3fd9ea664b9d4f31899ec5fdfdd31dc3c0391b66f292c0026ca9f6e49de6
|
Provenance
The following attestation bundles were made for squadron_sdk-0.1.1.tar.gz:
Publisher:
publish.yml on mlund01/squadron-sdk-py
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
squadron_sdk-0.1.1.tar.gz -
Subject digest:
6c51fcd52d30ba0036234c23408da5588c5500e62e32138551b478045c8a74e5 - Sigstore transparency entry: 1467382374
- Sigstore integration time:
-
Permalink:
mlund01/squadron-sdk-py@d4139cef2b3ea25fb83c34bcd5efbb995fe70634 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/mlund01
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@d4139cef2b3ea25fb83c34bcd5efbb995fe70634 -
Trigger Event:
push
-
Statement type:
File details
Details for the file squadron_sdk-0.1.1-py3-none-any.whl.
File metadata
- Download URL: squadron_sdk-0.1.1-py3-none-any.whl
- Upload date:
- Size: 13.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
feeeaab54e1d9bae45f83aeab5e7e69da26abc717c78be03c3f2c8b6c2b05b5c
|
|
| MD5 |
157aae7df7ad4796d6816f1a41a2d0e3
|
|
| BLAKE2b-256 |
ab70ebec277bb7068531b99b4285d15b27c8c2d732d291cf96303d25feed3615
|
Provenance
The following attestation bundles were made for squadron_sdk-0.1.1-py3-none-any.whl:
Publisher:
publish.yml on mlund01/squadron-sdk-py
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
squadron_sdk-0.1.1-py3-none-any.whl -
Subject digest:
feeeaab54e1d9bae45f83aeab5e7e69da26abc717c78be03c3f2c8b6c2b05b5c - Sigstore transparency entry: 1467382547
- Sigstore integration time:
-
Permalink:
mlund01/squadron-sdk-py@d4139cef2b3ea25fb83c34bcd5efbb995fe70634 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/mlund01
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@d4139cef2b3ea25fb83c34bcd5efbb995fe70634 -
Trigger Event:
push
-
Statement type: