> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tuneplane.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Plugin packages

> The manifest, the directory layout, and the publish-install-reference cycle.

```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
tp plugin publish ./my-plugin
```

A plugin artifact contains a strict `plugin.yaml` plus its implementation or declarations.
Python providers can use ordinary pure-Python wheels and capability-specific PyPA entry points.
The platform registry stores immutable artifacts; it does not install dependencies into jobs.

## Layout

```text theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
my-plugin/
├── plugin.yaml       # the manifest, required
├── README.md         # long description, optional, shown on the detail page
└── <your files>      # code for runtime extensions, declarations for assets and policies
```

## plugin.yaml

```yaml plugin.yaml theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
schema: tuneplane/plugin/v1
name: opsd-patch
version: 1.2.0
capability: algorithm
entrypoint: patch:install
load: eager
summary: OPSD teacher-sampling correction

requires:
  core: ">=0.3,<1"

# Shelf metadata — display and search only, never affects loading or security
icon: 🧩
author: Alice
license: Apache-2.0
homepage: https://github.com/alice/opsd-patch
keywords: [opsd, distillation]
```

### Required fields

**schema** `string` *required*

Always `tuneplane/plugin/v1`. Any other value is refused with the version this build speaks.

**name** `string` *required*

The leaf name. The platform stamps your account on it at publish time, so the published id is
`<owner>/<name>` — you do not write the owner yourself.

**version** `string` *required*

Starts with a letter or digit; letters, digits, `.`, `_`, `-` after that. Immutable once published.

**capability** `string` *required*

The one classification you declare: the protocol this package implements, one of `algorithm`,
`environment`, `data-prep`, `framework-integration`, `recipe`, `benchmark`, `playbook` or `prompt`.
Where its code runs and which shelf group it belongs to follow from it. Policy publication
requires an administrator; code never loads into the control plane.

**execution** `local | job`

Only for `data-prep`, the one capability with two execution locations: your own machine
(`local`) or a job (`job`). Every other capability has one location and refuses the field.

A manifest with any other `schema` is refused. The package type (runtime, asset or policy) is derived from the capability and is never declared.

### Entrypoints for runtime extensions

**entrypoint** `module.path:callable`

For source-directory providers, required for capabilities `algorithm`, `environment`, and `data-prep` with `execution: job`. A wheel provider instead declares `wheel` and `provider`. The module path is relative to the
package root, and must exist at publish time.

Forbidden for other capabilities and execution locations. A `data-prep` plugin with `execution: local` is discovered by the `prepare_*.py`
convention; a declarative pack executes nothing, so it has no entry point at all.

**load** `eager | deferred` (default: eager)

When the launcher calls your entrypoint. See [algorithm plugins](https://docs.tuneplane.ai/en/extend/algorithm-plugins.md).

### Optional fields

**requires.core** `PEP 440 specifier`

Which `tuneplane` versions your plugin works with, for example `">=0.3,<1"`. Checked at
launch; a mismatch stops the job with `plugin x@1.2.0 requires SDK >=0.3,<1, this one is 0.2.9`
rather than failing somewhere stranger later.

**summary** `string`

One line, shown in listings.

**icon** `string`

A preset icon name, an emoji (at most 8 characters), or a package-relative PNG path.
PNG artwork is limited to 64 KiB and 512 × 512 pixels. Defaults to the capability's icon.

**author** `string`

Who wrote it. Distinct from `owner`, which is the platform account that published it.

**license, homepage, keywords** `string, url, string[]`

Display and search only. `homepage` must be `http(s)`. At most 12 keywords, lowercased and
deduplicated.

### README.md

A `README.md` at the package root becomes the long description on the detail page. Capped at 64 KB;
anything beyond is truncated, because the detail page wants an introduction, not a manual.

It is deliberately not a manifest field — long text in YAML forces the author to escape things and
loses the Markdown toolchain. It is covered by the package digest, so changing it needs a new
version.

## Capabilities

You declare the capability; the execution location and the shelf group are derived from it. `data-prep` is the one capability with two execution locations, so it is the only manifest that also declares `execution`.

| Capability | Execution | Shelf group | Implementation | Who may publish |
| - | - | - | - | - |
| `algorithm` | `job` | Runtime extensions | Source entrypoint or wheel provider | Anyone |
| `environment` | `job` | Runtime extensions | Source entrypoint or wheel provider | Anyone |
| `data-prep` | `local` (declared) | Runtime extensions | `prepare_*.py` convention | Anyone |
| `data-prep` | `job` (declared) | Runtime extensions | Source entrypoint or wheel provider | Anyone |
| `framework-integration` | `job` | Runtime extensions | `integration.json` profiles and an isolated compiler | Anyone |
| `recipe` | `none` | Templates & evaluations | Recipe declarations and templates | Anyone |
| `benchmark` | `none` | Templates & evaluations | Benchmark declarations | Anyone |
| `playbook` | `none` | Management configuration | Deployment configuration | **Admin only** |
| `prompt` | `none` | Management configuration | Deployment configuration | **Admin only** |

The built-in framework integrations ship inside the SDK and are versioned with it. A new framework publishes an integration package and recipes against the existing compiler contract; it does not add a capability or require a core dispatch branch. New protocol behavior requires a versioned contract change; arbitrary execution locations are refused. [Solutions](https://docs.tuneplane.ai/en/guides/solutions.md) are not published as plugins.

> [!NOTE]
>
> `playbook` and `prompt` are deployment-wide singletons: one is active and it changes what everyone
> sees on a diagnosis page. They are admin-only because the consequence lands on other people. There
> are no per-user versions — "whose diagnosis verdict is this" is a question that should not exist.

## Publish

```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
tp plugin publish ./my-plugin
tp plugin publish ./my-plugin --owner platform-team   # admins only
```

The client validates the manifest and the layout locally, computes the directory digest, and
uploads. The server recomputes the digest and refuses a mismatch.

| Failure | Meaning |
| - | - |
| `409` | This version already exists. Versions are immutable — bump it |
| `400` | Manifest or layout problem; `detail` names the field |
| `403` | Publishing into a namespace that is not yours |

## Install and reference

```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
tp plugin ls                                      # what this deployment has
tp plugin info alice/opsd-patch                   # manifest, versions, digest
tp plugin install alice/opsd-patch                # download into ./tuneplane_plugins/
tp plugin install alice/opsd-patch --exp my-grpo  # and lock it to an experiment
```

`--exp` writes `plugins.lock.json` into the experiment: the reference and its digest. At submission
the platform reads that lockfile, injects exactly that version into the job package under
`tuneplane_plugins/<name>/`, and records the triple in the JobSpec.

```json plugins.lock.json theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
  "plugins": [
    { "id": "alice/opsd-patch", "version": "1.2.0", "digest": "sha256:9f2c…" }
  ]
}
```

> [!WARNING]
>
> Two plugins whose packages would unpack into the same leaf directory name cannot be combined in one
> job. Rename one.

## Check it worked

```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
tp plugin info alice/opsd-patch
```

Shows the version you just published and its digest. After submitting a job that references it, the
job log carries one line per loaded plugin:

```text theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
plugin  : alice/opsd-patch@1.2.0 applied in entrypoint process
```

If the digest was tampered with after injection, the job refuses to start instead:

```text theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
plugin alice/opsd-patch@1.2.0 content digest mismatch: spec=sha256:9f2c…, actual=sha256:41ab…
```

## Next

[Algorithm plugins](https://docs.tuneplane.ai/en/extend/algorithm-plugins.md) for what your entrypoint receives ·
[Recipe packs](https://docs.tuneplane.ai/en/extend/recipe-packs.md) · [Benchmark packs](https://docs.tuneplane.ai/en/extend/benchmark-packs.md)

## Standard Python providers

Build with your usual Python build backend. Register one callable under the group for its capability:

| Capability | Entry-point group | Consumer |
| - | - | - |
| algorithm | `tuneplane.algorithms` | Training entrypoint process |
| environment | `tuneplane.environments` | Job-side environment consumer |
| `data-prep` (`execution: job`) | `tuneplane.transforms` | Separate transform subprocess |

```toml theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
[project.entry-points."tuneplane.transforms"]
clean = "my_company_cleaner:transform"
```

```yaml theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
schema: tuneplane/plugin/v1
name: cleaner
version: 1.0.0
capability: data-prep
execution: job
api: "1"
wheel: my_company_cleaner-1.0.0-py3-none-any.whl
provider: clean
requires:
  core: ">=0.3.56,<1"
  python: ">=3.12"
  frameworks: {custom: ""}
  methods: [custom/custom]
```

`wheel` and `provider` replace `entrypoint`. Only portable pure-Python wheels without installation-time data relocation are supported. Native libraries and dependencies must already be in the job image. Wheel `Requires-Python` and `Requires-Dist` are checked in the job interpreter. Discovery inspects only the selected artifact's metadata; it never globally auto-loads installed providers.

Unknown manifest and requirement fields are errors. `requires.frameworks` maps framework names to version specifiers; an empty specifier constrains the name only. `requires.methods` lists method IDs. `requires.backends` lists allowed execution backends. Omitted constraints add no restriction, but do not bypass the capability's own supported consumer or the platform's resource requirements. Framework/method/backend conditions are checked before submission; Python and installed dependency conditions are checked in the actual job process. This is compatibility validation, not certification of arbitrary plugin code.

```sh theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
tp plugin validate ./cleaner
tp plugin publish ./cleaner
tp plugin install alice/cleaner@1.0.0 --exp <experiment>
```

Local downloads live under `tuneplane_plugins/<owner>/<name>/`. Download verification completes before replacing an existing installation. An experiment must explicitly lock each job plugin with `--exp`. Job bundles currently reject duplicate leaf names as well as overlapping Python import namespaces. Give each provider a unique import root.

## Shipped runnable examples

The Plugins page includes `tuneplane/jsonl-filter` and `tuneplane/text-records`, with dedicated icons, requirements, version controls and a README. These are real independently packaged providers, not catalog entries standing in for plugins. Administrators can disable them; restart preserves that choice. Their namespace is reserved for shipped artifacts.

* **JSONL filter:** structural validation, configurable text length, disk-backed exact deduplication, unchanged accepted records and rejection counts. Invalid records fail by default; an explicit skip policy is available. It does not claim semantic quality or PII detection.
* **Text records:** UTF-8 text/Markdown to overlapping character chunks, with stable IDs, source paths and offsets. Deterministic output supports auditing. It does not generate SFT instructions or parse PDFs.

Both require Python >= 3.12, SDK >= 0.3.56, framework `custom` and method `custom/custom`. They need no GPU or training library. Single-machine CPU-only jobs support Local, Node, Kubernetes and Slurm Fleets. Declare one pool with `nodes: 1`, `gpus_per_node: 0`, positive `cpus` and `memory_gb`, and no GPU series. Local/Node require Docker or Podman and configured host capacity. Kubernetes uses a native Pod and object-storage Capsule delivery. Slurm uses a registered SIF/SQSH runtime and the shared storage root, without Ray. Downstream training still needs a dataset schema accepted by its framework and method.

A transform callable accepts `tuneplane.contract.transforms.TransformRequest` and returns `TransformResult(files=(...), records=N)`. Its inputs are named readable paths, its configuration is a JSON object, and its output directory is owned by the host. All output files must be listed in the result. Symlinks and escaping paths are refused.

```json theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{"api":"tuneplane/transform/v1","inputs":{"documents":"input.jsonl"},"config":{"min_length":20}}
```

Run from the custom job's script, after locking the selected provider:

```sh theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
python -m tuneplane.transforms --plugin tuneplane/jsonl-filter \
  --request request.json --output "$TUNEPLANE_OUT_DIR/dataset" \
  --report "$TUNEPLANE_OUT_DIR/transform-result.json" --timeout 600
```

The destination must be new. The runner executes in a fresh subprocess, enforces a deadline, terminates its process group on cancellation, validates outputs and publishes the directory only after success. The report records the exact provider identity. This process boundary is lifecycle isolation, not a security sandbox; the job container supplies the execution boundary. Use the job's existing `output_dataset` declaration to publish the dataset.

## Deployment activation

Publishing a `playbook` or `prompt` stores an available version without changing deployment behavior. An administrator explicitly selects an exact version:

```sh theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
tp plugin activate alice/team-rules@1.2.0
tp plugin activate alice/team-rules@1.1.0  # rollback
tp plugin deactivate playbook            # built-in defaults
```

Activation stores the ID, version, digest and generation. Concurrent updates must present the current generation. Before disabling an actively selected plugin or version, restore platform defaults or explicitly apply another version. Disablement never silently changes the deployment policy. Replicas refresh eventually. A failed application preserves the prior process snapshot and is retried; the activation response reports whether the handling instance applied it. It is not a claim of simultaneous activation across every replica.

## Plugin artwork

Set `icon` in `plugin.yaml` to a preset (`filter` or `files`), an emoji, or a
relative PNG path such as `icons/logo.png`. Include the image in the published
package. PNG artwork must be at most 64 KiB and 512 × 512 pixels. External URLs
and paths outside the package are not accepted. The console serves the artwork
from registry metadata and falls back to a type icon if an image cannot load.

## Manage plugins in the console

The Plugins page lists packages registered on this platform. Use the card switch to enable or disable a whole plugin; the setting persists when another version is published. The Versions tab manages each release independently and selects the recommended default for new installations. Existing experiment locks remain unchanged. Without a recommendation, unpinned resolution retains the newest enabled release fallback; pin a version for reproducible use.

Upload a `tar.gz` containing `plugin.yaml` at its root (maximum 32 MiB). Preview validates the package without executing its code and displays its identity, compatibility requirements and digest. Confirming resends the same bytes with a short-lived review receipt; the server verifies both again. Browser imports start disabled. Enable the family and the selected version before use. Existing CLI publishing remains available.

Use & activity provides capability-specific instructions, policy Apply/Restore actions where applicable, and recorded job references. Non-administrators see only their own jobs. Unsubmitted local experiments are not counted. Policy application reports the responding process, not acknowledgement from every replica.

### Inspect a version before choosing it

Open a plugin's **Files & changes** tab to inspect the exact selected version, including disabled versions. It shows the recorded admission method and actor, the SHA-256 of the stored archive, and its unpacked content digest. Older packages without recorded provenance are labeled **Not recorded**; a hash does not establish publisher trust.

Search package paths and preview UTF-8 text up to 128 KiB and 4,000 lines. Binary files show size and SHA-256 without being executed or unpacked as nested archives. The server verifies the package content digest before returning inspection results. Invalid archives and digest mismatches are errors, not empty file lists.

Choose **Compare versions** and select a base version. The comparison runs from that base to the currently selected version, showing added, removed and modified files. Text differences are bounded to 2,000 lines and 256 KiB, with an explicit truncation notice. Comparing does not add, enable, recommend or activate a release. Add a new immutable version through Upload first, inspect it, then explicitly enable or recommend it as appropriate.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.