# 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

```hcl
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](../guides/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:

```hcl
plugin "shell" {
  source  = "./plugin_shell"   # path inside the project
  version = "local"
}
```

Rules:

- `version` must be `"local"` when `source` is a local path.
- The path is resolved against the current working directory and must
  stay inside the project root — `../../somewhere/else` is rejected at
  config-load time.
- The source directory must contain a `go.mod` (Go plugin) or a
  `pyproject.toml` (Python plugin); Squadron auto-detects which.
- For Go: `go` must be on `PATH`. For Python: `python3` must be on
  `PATH`. 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>`:

```hcl
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 installed
```

If `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](https://github.com/mlund01/squadron-sdk):

```go
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:

```bash
squadron plugin build myplug ./path/to/source
```

The build command runs `go build` from the source directory and writes
`runner.json`.

### Python

Use [squadron-sdk-py](https://github.com/mlund01/squadron-sdk-py):

```python
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`:

```toml
[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:

```bash
squadron plugin build myplug ./path/to/source
```

The 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](https://github.com/mlund01/squadron-sdk-py).

## 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:

```hcl
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`:

```bash
squadron plugin build myplug ./path/to/source
```

```hcl
plugin "myplug" {
  version = "local"
}
```

## CLI Helpers

```bash
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)
```
