---
title: Plugins
description: Plugins are small folders that give Lampson new tools — a database query, your company's API, a deploy — in any language. Off by default; you turn each one on.
---
# Plugins

A **plugin** is a folder that teaches Lampson a new tool. Query your database, call your company's API, run a deploy, post a message — anything a script can do, in any language. Plugins are **off by default**: you turn each one on, and only then can the agent use its tools.

> Plugins were called *lamps* until Lampson 0.2.6. The name now belongs to [lamps.sh](https://lamps.sh) — see [Plugins and lamps](#plugins-and-lamps) below. Old folders keep working: [what still works](#renamed-from-lamps).

## Turning one on

```
❯ /plugins on postgres
☼ postgres is on — 1 tool available: plugin_postgres_query
```

In the web UI, click the **plugins** pill in the header: a list of every plugin with a switch, its tools and a form to try each one yourself. `/plugins off <name>` turns it off. The agent can *ask* you to turn a plugin on, but it can never do it by itself.

## Anatomy

```
~/lampson/plugins/postgres/      ← global: available in every project
  plugin.json                    ← the manifest
  query.py                       ← the code, in any language

my-project/.lampson/plugins/…    ← or inside one project (commit it, and your team has it)
```

`plugin.json`:

```json
{
  "name": "postgres",
  "description": "read-only queries on the dev database",
  "kind": "exec",
  "command": "python query.py",
  "timeout": 30,
  "tools": [
    {
      "name": "query",
      "description": "Run a SELECT and return the rows as JSON.",
      "parameters": {"type": "object", "properties": {"sql": {"type": "string"}}, "required": ["sql"]},
      "readonly": true
    }
  ]
}
```

## How a call works

Every time the agent uses a plugin tool, Lampson starts **one short process** and passes the call in environment variables:

| variable | content |
|---|---|
| `PLUGIN_TOOL` | which tool was called (`query`) |
| `PLUGIN_ARGS` | its arguments, as JSON (`{"sql": "select …"}`) |
| `PLUGIN_DIR` | the plugin's folder |
| `PLUGIN_WORKSPACE` | the project folder |

Whatever the script prints to stdout is the result the model sees. A non-zero exit or a timeout becomes an error the model can read.

```python
# query.py
import os, json, psycopg
tool = os.environ["PLUGIN_TOOL"]
args = json.loads(os.environ.get("PLUGIN_ARGS", "{}"))
if tool == "query":
    with psycopg.connect(os.environ["DEV_DATABASE_URL"]) as db:
        rows = db.execute(args["sql"]).fetchall()
    print(json.dumps(rows, default=str))
```

## Two kinds

- `"kind": "exec"` — `command` runs as is, from the project folder. Any language. A word in the command that names a file in the plugin's folder is resolved automatically (`python query.py` finds the plugin's `query.py`). No sandbox: the script can do whatever your user can.
- `"kind": "syn"` — a [Synsema](https://synsema.org) program (`"entry": "plugin.syn"`). It runs under a **capability ceiling** taken from `"caps"` in the manifest: the plugin can do exactly what you approved when you turned it on, and code that asks for more fails. Example: `"caps": "file.read=workspace/*,net"`.

Because an `exec` plugin has no such ceiling, turning any plugin on is always a human decision. Your code, your rules, your risk.

## Permissions

Plugin tools follow the [permission](/en/docs/permissions) mode: they ask in `ask`, run in `yolo`, are denied in `strict`. Tools marked `"readonly": true` are also available to the read-only profiles (`plan`, `review`, `explore`).

## Let the agent write one

"Make me a plugin that lists open pull requests with `gh`." The agent creates the folder and manifest inside your project, validates it (`synsema check` for a Synsema plugin), and tells you it is ready — turning it on is still yours. In the web chat a `☼ plugin created` line appears and the plugins pill blinks.

## Scheduling a plugin

A plugin tool can be the job of a [scheduled task](/en/docs/schedules): "every morning, run `postgres.query` with this SQL and send me the result". Turning the plugin on is the authorization.

## The example

`~/lampson/plugins/example-hello/` ships with Lampson — a Synsema plugin with one tool, `greet`. Copy the folder, rename it, and you have a starting point.

## Plugins and lamps

Two different things, on purpose:

- A **plugin** is local to you and your repo: any language, no distribution, no ceiling unless you write it in Synsema. Quick to make, yours to trust.
- A **lamp** ([lamps.sh](https://lamps.sh)) is a portable unit of capability: a manifest that declares exactly what the code may touch, a runtime that enforces it and audits every check, versioned by git tag, pulled from a hub, usable by *any* MCP agent — Claude Code, Cursor, Lampson…

To use lamps in Lampson: install the CLI, `lamp add <ref>`, enable it, and add `lamp mcp` as an [MCP server](/en/docs/mcp). Its tools then arrive with their ceiling stated in the description.

## Renamed from lamps

If you were using lamps in Lampson before 0.2.7, nothing breaks today:

- `.lampson/lamps/<name>/` folders are still discovered (marked *old folder* in the UI and in `/plugins`); a missing `plugin.json` falls back to `lamp.json`.
- The on/off state in `.lampson/lamps.json` is read until `.lampson/plugins.json` exists.
- Scripts still receive `LAMP_TOOL`, `LAMP_ARGS`, `LAMP_DIR` and `LAMP_WORKSPACE` next to the new names; a Synsema plugin may keep `require env("LAMP_*")`.
- Scheduled tasks saved with `"type": "lamp"` keep running; `/lamps` is an alias of `/plugins`.
- Global folders you put in `~/lampson/lamps/` are moved to `~/lampson/plugins/` the first time you run Lampson.

This is a bridge, not a second format: rename the folder to `.lampson/plugins/`, the manifest to `plugin.json`, and read the `PLUGIN_*` variables.
