Migrating from tiles to plugins

Guide for migrating from the legacy tile.json format to the new plugin format

Tessl is transitioning from tiles to plugins as the packaging format for shareable agent rules and skills. This document explains why, what changed, and how to migrate your existing tile packages.

Why the change?

Tessl started with tiles: prepackaged bundles of context for agents. Since then, the wider agent ecosystem is settling on plugins as the standard format for the same idea, so we're moving Tessl onto plugins.

For you, this means what you build in Tessl travels naturally to different coding agents, and the terminology lines up with what your teams are already seeing. For us, it's a stronger foundation to build on. As agents start handling richer kinds of context, plugins give us the base to layer versioning, distribution, evaluation, and security on top of these new context types.


What changed

Manifest location and filename

Format
File
Location

Tile

tile.json

Package root

Plugin

plugin.json

.tessl-plugin/plugin.json

The manifest moves into a dedicated .tessl-plugin/ directory. This keeps the root clean and makes space for sibling agent manifests (.claude-plugin/, .cursor-plugin/) without naming collisions, if and when they are added.

Field-by-field comparison

Concept
tile.json
plugin.json
Notes

Package name

name

name

Same format: workspace/package

Version

version

version

Required at publish in both

Short description

summary

description

Field renamed; trimmed and required at publish

Private flag

private

private

Identical

Repository URL

repository

repository

Both accept https:// URLs

Package author

author

New: { name, email, url } object

Homepage

homepage

New: URL string

License

license

New: SPDX identifier, e.g. "MIT"

Skills

skills: { "name": { path: "..." } }

skills: "./skills/"

Changed from keyed object to path(s)

Rules / Steering

rules: { "name": { rules: "..." } }

rules: "./rules/"

Changed from keyed object to path(s)

Commands

commands: { "name": { script: "..." } }

commands: "./commands/"

Changed from keyed object to path(s)

Docs file

docs: "./README.md"

Removed; move content into skills or rules

Describes (PURL)

describes: "pkg:..."

Removed; not carried forward

Skills: from keyed registry to directory discovery

In tile.json, each skill had an explicit key and path:

In plugin.json, you point at a directory and Tessl discovers skills automatically. A skill's identity comes from the name: field in its SKILL.md frontmatter (falling back to the directory basename if no frontmatter is present):

You can also pass an explicit array of paths when you want precise control:

Rules: from nested objects to directory paths

In tile.json:

Note: steering was accepted as an alias for rules in tile.json and normalised automatically. Both wrote to the same field internally.

In plugin.json:

All .md files found in the declared directory are included as rules. There are no longer named keys; the rule name is derived from the filename.

Docs and Describes: removed

The docs field (a standalone markdown file) and describes field (a PURL for a versioned dependency) have no equivalent in plugin.json.

  • docs: Fold the content into a rule or skill. If the doc was user-facing guidance about a tool or library, it belongs as a skill.

  • describes: This field declared that a tile described a specific versioned package (e.g. a Go library). It was used as part of Docs, as Docs have not been carried forward, neither has Describes.

If you publish with TESSL_PLUGIN=1 and your tile.json still has docs or describes set, the CLI will warn you at pack time that these fields will not be included.

For converting docs to skills, there is a skill available in the Tessl Registry.


Compatibility during the transition

Tiles and plugins coexist. The CLI continues to read and publish tile.json packages without any changes required. No existing tile breaks.

When both a tile.json and a .tessl-plugin/plugin.json are present in the same package .tessl-plugin/plugin.json takes precedence for metadata (name, version, description).


How to migrate

Using the migrate command

The CLI can generate .tessl-plugin/plugin.json from your existing tile.json automatically:

Run this from your package root (the directory containing tile.json). The command:

  • Creates .tessl-plugin/ if it doesn't exist

  • Translates all supported fields (name, version, summary → description, repository, private, skills, commands, rules)

  • Warns if your tile.json contains docs or describes — these have no plugin equivalent and will not appear in the output

If you want to migrate a package that isn't in the current directory, pass the path:

What the command does not do

The migrate command handles the mechanical translation. A few steps still require your attention:

  • docs and describes: If your tile.json uses these fields, the command will warn you and omit them. See Docs and Describes: removed for how to fold that content into skills or rules.

  • SKILL.md frontmatter: The command copies your skill paths as-is. If your SKILL.md files are missing a name: field, Tessl will fall back to the directory basename — but adding frontmatter is recommended. See Step 4: Add SKILL.md frontmatter.

  • tile.json is kept: The command does not delete your existing tile.json. Once you've verified the new manifest with tessl lint and tessl publish --dry-run, you can remove it manually.


Alternatively:

Step 1: Create .tessl-plugin/plugin.json

Create the directory and manifest:

Translate your tile.json fields:

tile.json (before):

.tessl-plugin/plugin.json (after):

Step 2: Check your directory layout

The path values in plugin.json are relative to the package root (i.e. the directory containing .tessl-plugin/). Verify that the paths you declared actually exist:

Step 3: Handle docs and describes

If your tile.json has a docs field:

  1. Open the file it points to.

  2. If the content is a step-by-step procedure for the agent to follow, convert it to a skill: create a SKILL.md in your skills directory with the content.

  3. If the content is constraints or style guidance, move it into your rules directory as a .md file.

  4. Remove docs from tile.json (or skip it entirely if you're dropping tile.json).

With tile.json, skill identity was the key in the skills object. With plugin.json, identity comes from the name: field in SKILL.md frontmatter. Add frontmatter if it's missing:

If there's no frontmatter, Tessl uses the directory basename as the skill name — so existing skills continue to work without changes.

Step 5: Verify with the CLI

Run the linter to catch any issues:

In plugin mode (TESSL_PLUGIN=1), the CLI will:

  • Warn if tile.json is still present with docs or describes fields that have no plugin equivalent.

  • Validate that every path declared in plugin.json resolves and contains recognisable content.

Do a dry-run pack to confirm what will be included in the tarball:

Step 6: Publish

Once you're confident in the new format, you can delete tile.json from your repository to remove the duplication.


Running evaluations after migration

tessl eval run works the same way it did for tiles: point it at the package root. The only change is where that root is.

Before migration, you pointed at the tile directory (where tile.json lives):

After migration, point at the plugin directory (where .tessl-plugin/plugin.json lives):

The plugin directory is the parent of .tessl-plugin/, not .tessl-plugin/ itself. From a plugin root, this is usually just .:


Quick reference

tile.json → plugin.json field mapping

tile.json
plugin.json

summary

description

skills.X.path

skills (directory or array)

rules.X.rules

rules (directory or array)

steering.X.rules

rules (directory or array)

commands.X.script

commands (directory or array)

docs

No equivalent — move to skills or rules

describes

No equivalent — contact us

name, version, private, repository

Identical

description (required at publish)

author, homepage, license (optional)

Minimum valid plugin.json

By convention, skills will be pulled from the ./skills directory and rules from ./rules. If they are not set, these conventions will be used.

Last updated