# extension.toml

> Every table and field of an extension's manifest, what each one means, and the rules core and the node check it against.

Every extension is a folder with an `extension.toml` at its root. Core, the node and the apps all
read the same file, and none of them takes one that breaks a rule on this page.

```toml
[extension]
id = "tickets"
name = "Tickets"
version = "0.2.0"
publisher = "acme"
summary = "Open, update and close tickets from a thread."
platforms = ["macos-arm64", "linux-x86_64"]

[run]
command = ["python", "-m", "tickets"]
runtime = "python3.12"

[permissions]
network = true
secrets = ["tickets.key"]

[[commands]]
kind = "tickets.open"
title = "Open a ticket"
params = { type = "object", required = ["title"], properties = { title = { type = "string" } } }

[[tools]]
name = "tickets_open"
command = "tickets.open"
description = "Open a ticket in the team's tracker, with this title."
returns = "result"
```

[Extensions](/guides/extensions/) explains how they're installed and used. The tables below are in
the order they usually appear.

## `[extension]`

Required. What it is.

| Field | Default | Meaning |
|---|---|---|
| `id` | required | Lowercase letters, digits and `_`, 2 to 32 characters, starting with a letter. It prefixes every command, tool and event name, and names the repo: `pointman-<id>`. |
| `name` | required | What people see, up to 60 characters |
| `version` | required | A semantic version: `0.2.0`, `1.0.0-beta.1` |
| `publisher` | required | Who publishes it, as an id like `acme` |
| `summary` | required | One line, up to 200 characters |
| `node` | `">=0.1,<1"` | The node versions it works with, as a range |
| `platforms` | `[]` | Where it runs: `macos-arm64`, `macos-x86_64`, `linux-x86_64`, `linux-arm64`, `windows-x86_64`. Required when it has `[run]`. |

## `[run]`

The process the node starts for it, from the package's folder. Leave it out for a behaviour, or for
a service that runs in core rather than on a node. Commands, tools, events, an MCP server, session
hooks and credentials all need it.

| Field | Meaning |
|---|---|
| `command` | The argv to run, such as `["python", "-m", "tickets"]`. For a compiled one, `["bin/<id>", …]`. |
| `runtime` | `python3.12` (or another `python3.x`): the node makes an environment from the package's lockfile. Or `binary`, for a compiled extension (below). |

**Compiled extensions** (`runtime = "binary"`) ship one build per platform. Each release on GitHub
carries one asset per platform in `platforms`, named `<id>-<version>-<platform>.tar.gz`, with the
extension's files at its top, the same `extension.toml`, and the program at `bin/<id>`. Core
refuses a release that's missing one, and each node fetches the build for its own platform. The
node runs `bin/<id>` from the package's folder, never a program of that name on your `PATH`.

## `[detect]`

Paths whose presence means a machine could use it, per platform: `macos`, `linux`, `windows`. The
apps use them to suggest machines ("Installed on studio; add it for the agents there?"). They're
suggestions only.

```toml
[detect]
macos = ["/Applications/Blender.app"]
linux = ["/usr/bin/blender"]
```

## `[permissions]`

What it may touch on the machine. The node holds it to these.

| Field | Default | Meaning |
|---|---|---|
| `network` | `false` | It reaches the network |
| `gpu` | `false` | It uses the GPU |
| `drives_apps` | `[]` | Other apps on the machine it controls, such as DaVinci Resolve |
| `secrets` | `[]` | Vault secrets it may use, by name, or every name under a prefix: `rota.*` |
| `folders` | `[]` | Folders it reads or writes beyond its own work folder |

## `[settings]`

A JSON Schema (`type = "object"`) for its settings on each machine. The apps draw it as a form, and
a machine's own values go in its `agent.toml` under `[extension_settings.<id>]`. Every `default`
must fit its own schema.

```toml
[settings]
type = "object"

[settings.properties.threshold]
type = "integer"
minimum = 50
maximum = 100
default = 90
description = "Move new runs off an account once either window is this full (%)"
```

## `[[commands]]`

Work agents can ask a machine for, with `run_on_machine` or a tool below.

| Field | Default | Meaning |
|---|---|---|
| `kind` | required | `<id>.<verb>`, such as `tickets.open` |
| `title` | required | What people see, up to 60 characters |
| `params` | required | A JSON Schema (`type = "object"`) for its parameters, checked before anything runs |
| `lane` | none | Commands that share a lane run one at a time on a node, such as `gpu` |
| `cancellable` | `false` | It can be stopped while it runs |
| `resumable` | `false` | It saves checkpoints and carries on after the node restarts |
| `ready_when` | none | One field of its status that must be true before core sends it work |
| `outputs` | `[]` | What it makes, so the apps know how to show it: `image`, `video`, `sequence`, `model`, `page`, `audio`, `document`, `progress` |

## `[[tools]]`

MCP tools for agents, each one making a request of a command. The command's `params` are the
tool's input.

| Field | Default | Meaning |
|---|---|---|
| `name` | required | `<id>_<what it does>`, such as `tickets_open` |
| `command` | required | One of its own command kinds |
| `description` | required | What agents are told it does, up to 1,000 characters |
| `returns` | `"accepted"` | `accepted`: the request's id at once. `result`: wait for the result. |

## `[[events]]`

Status it reports to core while it runs.

| Field | Meaning |
|---|---|
| `name` | `<id>.<noun>`, such as `tickets.queue` |
| `description` | What it holds |

## `[[files]]`

File types it knows, so the apps can preview them and the Mac can open them.

| Field | Default | Meaning |
|---|---|---|
| `extensions` | required | Like `[".blend"]` |
| `open_with` | none | The app a Mac opens them in |
| `preview` | none | One of its commands, which makes something every app can show |

## `[mcp]`

An MCP server it brings. The node signs in to it with OAuth and makes the calls, and agents' sessions
reach it through the node. An agent gets it with [`/agent`](/reference/slash-commands/#agent).

| Field | Default | Meaning |
|---|---|---|
| `url` | required | The server's Streamable HTTP address |
| `scopes` | `[]` | OAuth scopes to ask for. None: what the server says. |
| `instructions` | none | What sessions are told about it, if the server says nothing, up to 2,000 characters |

## `[[links]]`

Links to its service that the apps draw as pills. See
[Links as pills](/guides/extensions/#links-as-pills).

| Field | Meaning |
|---|---|
| `kind` | The service's name for it, such as `page` |
| `match` | What the link looks like, with `{placeholders}`: `https://pages.example.com/@{owner}/{page}` |
| `show` | What the pill shows, in order: `title`, `repo`, `number`, `sha`, `state`, `checks`, `lines` |

## `[behavior]`

Makes it a behaviour: guidance for agents, with no process, added to the wake prompt of each agent
it's switched on for. A behaviour has no `[run]`, `[[links]]` or `[[files]]`. See
[Behaviours](/guides/extensions/#behaviours).

| Field | Default | Meaning |
|---|---|---|
| `skill` | `"SKILL.md"` | The file with the guidance, frontmatter first |
| `clients` | all three | The clients it's written for: `claude`, `codex`, `gemini` |
| `files` | `[]` | Other files or folders in the package that agents read when a task needs them, named in the skill by relative path |

## `[sessions]`

Hooks into the agent runs its node starts: it's asked before each run what to add to the run's
environment, and told how the run ended and how its account's limits stood. Rota picks each run's
account this way.

| Field | Default | Meaning |
|---|---|---|
| `clients` | `["claude"]` | The clients whose runs it hears about |
| `env` | required | The environment variables it may set on a run, such as `CLAUDE_CODE_OAUTH_TOKEN`. The node drops any other. `PATH`, `HOME` and the node's own variables can't be set. |

## `[[credentials]]`

Something it needs from a person, such as an API key, drawn by the apps as a form. See
[Give an extension what it needs](/guides/extensions/#give-an-extension-what-it-needs).

| Field | Default | Meaning |
|---|---|---|
| `id` | required | What it is, within the extension |
| `title` | required | What the apps call it, up to 80 characters |
| `help` | none | Where to get the value, up to 400 characters |
| `item` | required | The vault item it's kept as: `<id>.<name>`, or `<id>.{label}` to ask for a label. `[permissions] secrets` must cover it. |
| `value` | any text | A regular expression the whole value must match |
| `placeholder` | none | A hint in the empty field |
| `needed` | `1` | How many the apps ask for |
| `sign_in` | `false` | `true`: it signs the account in itself, and the apps offer **Sign in** |
