---
title: Agents as files
description: Define Lampson agents in a JSON file — their prompt, exact tools, limited paths, per-tool permissions, own model — per project or global.
---
# Agents as files

Besides the agents that ship with Lampson (`build`, `plan`, `review`, `explore`), you can define your own in a file. An agent is **data, not code**: its prompt, the tools it may use, its permissions and its model.

```json
{
  "id": "tienda",
  "description": "Serves the bakery's customers over chat",
  "base": "none",
  "system": "You are the assistant of Sol Bakery. Read the catalog before answering about prices or stock. Never make up products or discounts.",
  "tools": [{"name": "read", "paths": [".lampson/tienda/*"]}],
  "permission": "strict",
  "steps": 6,
  "budget_tokens": 40000,
  "model": {"provider": "", "model": ""}
}
```

Save it as `.lampson/agents/tienda.agent.json` in your project: the file name must be `<id>.agent.json`. From then on you see it in `/agent`, in the web picker, in `lampson run --agent tienda`, in the API, in channels and in the SDK.

## Where they live

- **Project**: `<project>/.lampson/agents/<id>.agent.json`. Commit it and your team has it.
- **Global**: `~/lampson/agents/<id>.agent.json` — in the Lampson folder, available in **all** your projects. A `reviewer` with your rules, a `translator`: you write them once.
- If a project has an agent with the same `id` as a global one (or a built-in one), **the project's wins**.

A broken file doesn't take down the others: it is ignored and the reason is shown.

## The fields

| field | | default |
|---|---|---|
| `id` | lowercase letters, digits and `-` (max. 40) | required |
| `description` | what it's for; shown in `/agent`, the API and to AIs that use it over MCP | `""` |
| `base` | `"none"`: the prompt is just `system`. `"lampson"`: prepends the coding agent's prompt (tools, project rules, `AGENTS.md`) | `"none"` |
| `system` | the agent's prompt (required with `base: "none"`). The [instructions](/en/docs/plugins#instructions-the-plugin-brings-its-process) of the plugins whose tools it has are added to it | |
| `tools` | the tools it may use — see below | `[]` |
| `permission` | `ask`, `strict` or `yolo` — see [Permissions](/en/docs/permissions) | the way in's |
| `steps` | max steps per turn | Lampson's |
| `budget_tokens` | max tokens per turn | Lampson's |
| `model` | `{"provider", "model"}` so this agent uses its own model (a cheap one for customer service, a strong one for code) | the configured one |
| `buffer` | the [mailbox](/en/docs/api#the-mailbox)'s quiet wait: `{"quiet_s": 3, "max_s": 15}` or `false` | the way in's |

Its own `model` is honored everywhere: the API, channels, the SDK and the terminal too — `lampson --agent tienda` starts with `tienda`'s model, and `/agent` switches model along with the agent (`/model` still changes it for the session).

## Tools and permissions

`tools` is an **exact list**: the model sees nothing outside it.

```json
"tools": [
  "read",
  "plugin_pedidos_*",
  {"name": "read", "paths": [".lampson/tienda/*", "README.md"]},
  {"name": "mcp_proveedor_ask", "approve": "auto"},
  {"name": "bash", "approve": "ask"}
]
```

- **By name**: built-in tools (`read`, `write`, `edit`, `ls`, `find`, `grep`, `bash`, `fetch`…), plugin tools (`plugin_<plugin>_<tool>`) and MCP tools (`mcp_<server>_<tool>`). A trailing `*` matches a prefix: `plugin_pedidos_*`.
- **`paths` — limited paths** (optional). Only for `read`, `write`, `edit`, `ls`, `find` and `grep`. With plain `"read"` the agent sees the whole project (as usual, ideal for code or personal use); with `paths`, only those paths: `"folder/*"` is the folder and everything inside it, `"file.json"` an exact file, `"*"` everything. Lampson checks the **real** path the tool would use, not the model's text: `../`, backslashes and absolute paths don't slip through.
  An agent with limited paths **cannot** also have `bash`, `process`, `delegate`, `lsp` or `computer_use`, which would read any file another way: Lampson rejects the file and tells you why.
- **`approve` — per-tool permission** (optional). `"auto"`: that tool, and only that one, runs without asking for approval even if the agent is `strict` (what is forbidden in every mode, like `rm -rf /`, stays forbidden). `"ask"`: always asks for approval, even if the agent is `yolo`. So you don't have to give a whole agent `yolo` just so it can use one tool.

**With nobody present** (the API, a channel, `lampson run`), an agent that doesn't set `permission` runs in `strict`: whatever would ask for approval is denied, and the model sees it and explains it. Over the API, an agent in `ask` can wait for an approval: it shows up in the web UI's **Approvals** panel and, if you set up the public URL and the webhook, on your phone. → [Scheduled tasks](/en/docs/schedules)

## A public-facing agent

For a bot that talks to strangers, the safe choice is to give it just enough:

- `base: "none"` and a `system` that says what it does and what it doesn't.
- Only the tools it needs — better a plugin with one concrete action (`catalogo_buscar`) than `read`; and if it uses `read`, with `paths`.
- `permission: "strict"`.

A complete example, with a real conversation: [Recipe: taking orders by chat](/en/docs/orders).

Even if someone talks it into "reading the `.env`", the agent can't: Lampson rejects the call before running it. What Lampson guarantees and what it doesn't, with a real prompt injection example, is in [Security](/en/docs/security); and a support agent with its own tools is in [Recipe: a support agent](/en/docs/support).
