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
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
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 existTranslates 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
.tessl-plugin/plugin.jsonCreate 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:
Open the file it points to.
If the content is a step-by-step procedure for the agent to follow, convert it to a skill: create a
SKILL.mdin your skills directory with the content.If the content is constraints or style guidance, move it into your rules directory as a
.mdfile.Remove
docsfromtile.json(or skip it entirely if you're droppingtile.json).
Step 4: Add SKILL.md frontmatter (recommended)
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.jsonis still present withdocsordescribesfields that have no plugin equivalent.Validate that every path declared in
plugin.jsonresolves 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
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

