---
title: Headless (lampson run)
description: Use a Lampson agent from a script, a cron job or another program — a message in, the answer out, as text or JSON events, or one open process with --stdio.
---
# Headless: `lampson run`

The same agent, without the terminal UI or the web: a message goes in, the answer comes out. For scripts, cron jobs, other programs.

```sh
lampson run "summarize today's changes"                         # the answer on stdout
lampson run --agent plan "review the last commit"               # any agent, built-in or yours
echo "hello" | lampson run --agent tienda --thread cliente-42   # the message can also come on stdin
lampson run --session 20261016-122626 "and the tests?"          # continue a known session
```

`lampson run` doesn't start the hub or the web server: it prepares the project folder and runs the turn.

## Options

| option | |
|---|---|
| `--agent ID` | the agent that answers (default: `build`) |
| `--thread T` | the conversation's identity outside Lampson: a phone number, a ticket, `cto:tarea-17`. **Same agent + same thread = same conversation**, with its history |
| `--session ID` | or continue a session by its id |
| `--json` | each step as a JSON line (see below) |
| `--ask` · `--strict` · `--yolo` | the permission mode; with none, the agent's, and if it doesn't say, `strict` |

With nobody present, whatever would ask for approval is **denied**: the model sees it and explains it in its answer.

**Exit code**: `0` ok · `1` the turn ended in an error · `2` invalid usage (unknown agent, empty message).

## `--json`: the events

One JSON line per event. First `session`, then what the agent does, and `done` at the end:

```json
{"kind": "session", "data": {"session": "20261016-125226-8941", "agent": "tienda", "thread": "cliente-42", "mode": "strict"}, "agent": "tienda", "thread": "cliente-42"}
{"kind": "tool_call", "data": {"name": "read", "args": {"path": ".lampson/tienda/catalogo.json"}}, "agent": "tienda", "thread": "cliente-42"}
{"kind": "tool_result", "data": {"call": {…}, "output": "…"}, "agent": "tienda", "thread": "cliente-42"}
{"kind": "assistant", "data": "Yes, we have whole-wheat bread at $3,200.", "agent": "tienda", "thread": "cliente-42"}
{"kind": "done", "data": {"agent": "tienda", "thread": "cliente-42", "session": "…", "text": "Yes, we have whole-wheat bread at $3,200.", "steps": 2, "usage": {"input": 1079, "output": 357, "cached": 448}, "stopped": "done"}, "agent": "tienda", "thread": "cliente-42"}
```

The event kinds: `session`, `assistant`, `tool_call`, `tool_result`, `tool_denied`, `usage`, `inbox`, `error`, `compact` and `done`. In `done`, `stopped` is `done`, `max_steps`, `budget` or `error: …`.

## `--stdio`: one open process (local mode)

`lampson run --stdio` stays open and handles many requests: one per line on stdin, each with an `id` of yours. It's what the [SDK](/en/docs/sdk) uses in local mode — Lampson inside your program, no server.

```json
{"id": "r1", "op": "ask", "to": "tienda", "thread": "cliente-42", "text": "do you have whole-wheat bread?"}
{"id": "r2", "op": "agents"}
{"id": "r3", "op": "ping"}
```

Stdout carries the same events as `--json`, each with its request's `id`, and a `done` when each `ask` finishes. On start it emits `{"kind": "ready", "data": {"agents": […], "protocol": 2, "warnings": […]}}`: in `warnings`, one line per tool some agent lists but will not have (a plugin that is off, a typo). A `warning` event also comes before an `ask` to that agent. With `LAMPSON_BUILTIN_TOOLS=off` the process offers no tool of Lampson's own (the SDK's `lampsonTools: false`).

- **Different** conversations run in parallel; two requests on the **same** conversation (agent + thread) are handled in order.
- `"mode": "strict"` or `"yolo"` in an `ask` sets the permissions for that request (it's your program, on your machine).
- Your app's tools (what the SDK's `lampson.tool()` uses): `{"op": "tools", "tools": [{"name", "description", "parameters", "timeout"}]}` replaces the whole list. When the model calls one, out comes `{"kind": "tool_request", "data": {"call_id", "name", "args", "ctx"}}`, and your program answers `{"op": "tool_result", "call_id", "output"}` (or `"error"`). `ctx` is the request's context (`from`, `thread`, `meta`…), which the model does not write.
- A line that isn't JSON returns an `error` and the process keeps going. Closing stdin ends it once the requests in progress finish.
