lampson 0.2.3 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.env.example CHANGED
@@ -1,6 +1,6 @@
1
1
  # Lampson — copia a .env (gitignored). Las claves nunca entran al proceso del programa: secret() es opaco.
2
2
 
3
- # Proveedor: openai | deepseek | kimi | groq | grok | openrouter | ollama | anthropic | minimax
3
+ # Proveedor: openai | deepseek | kimi | groq | grok | openrouter | ollama | anthropic | minimax | glm
4
4
  LAMPSON_PROVIDER=deepseek
5
5
  LAMPSON_API_KEY=sk-...
6
6
 
package/README.md CHANGED
@@ -1,480 +1,101 @@
1
1
  # Lampson
2
2
 
3
- **A coding agent harness written in [Synsema](https://synsema.com).** You point it at a project; it
4
- reads, searches, edits and runs commands through tools that are confined to that project by the
5
- language itself and every step is visible, in the terminal or in a web UI.
3
+ **A coding agent that lives in your project.** Open it inside a folder, say what you want in plain words,
4
+ and it reads, edits and runs showing you every step and **asking before anything risky**. Terminal or
5
+ browser. Works with the model you already pay for.
6
6
 
7
- - **Workspaces, always on**: every project folder is a *workspace* with its own process (sessions, scheduled tasks,
8
- MCP, lamps, dev servers), all behind one hub at http://127.0.0.1:8080 open several at once and switch without
9
- killing the others. `lampson` (terminal) and `lampson --web` (browser) open the workspace of the current folder;
10
- `lampson --install` keeps the hub alive across logins.
11
- - **Any OpenAI- or Anthropic-compatible API** over raw HTTP: DeepSeek, Kimi, Groq, Grok, OpenRouter,
12
- Ollama, Anthropic, MiniMax… one `.env` line to switch.
13
- - **Least-privilege by construction**: each tool declares its capabilities; the runtime enforces
14
- them. File tools can only touch the mounted workspace (absolute paths, `..`, sibling dirs → denied).
15
- - **Permissions you control**: `ask` (approve dangerous commands, in the terminal or with a button in
16
- the web UI), `yolo`, `strict`. Destructive system commands are always blocked.
17
- - **Agents**: `build` (edits), `plan` (read-only), `review` (runs tests, never edits), `explore`,
18
- `worker` (scoped implementation). **Sub-agents**: `delegate` runs several of them *in parallel*
19
- (real threads) with their own context and a restricted toolset, or in the *background* — the report
20
- lands in the parent's inbox when it finishes; every child has a live log (`/agents`, web panel).
21
- - **Managed processes**: the agent starts servers with `process`, and the new log lines of every
22
- server are appended to each command result — it *sees its own console*. Live logs in the web UI.
23
- - **Skills**: Markdown procedures the model loads on demand (yours in `workspace/skills/`).
24
- - **MCP servers**: global (`lampson/.lampson/mcp.json` — one config, every project you open) or per
25
- project (`.lampson/mcp.json` in the repo), same JSON as Claude Code / Cursor. Their tools join the
26
- model's catalog as `mcp_<server>_<tool>`; calling one asks you first (yolo allows, strict denies).
27
- - **LSP**: the agent navigates code through the project's language servers — `lsp symbols` gives a
28
- file's structure without reading it, `definition`/`references`/`hover` resolve what grep leaves
29
- ambiguous. Nothing is bundled: `/lsp add typescript|python|rust|go|css|html` (or your own command),
30
- global or per project; each server starts on its first query.
31
- - **Lamps** (tool plugins): a folder with a `lamp.json` manifest plus code — a Synsema program that runs
32
- under a capability ceiling, or any executable (js, py, sh…). Global in `lampson/lamps/<name>/`, per
33
- project in `.lampson/lamps/<name>/` (the agent can write those). **Off by default**: you turn them on
34
- from the top bar of the web UI or `/lamps on <name>`; their tools join the catalog as `lamp_<lamp>_<tool>`.
35
- - **Scheduled tasks**: "every 6h", "daily 09:00", "mon,wed 08:30" — a lamp tool, a fixed shell command, or a
36
- full unattended agent run from a prompt with a permission envelope fixed when you create it (`strict` /
37
- `ask` / `yolo`). They run inside Lampson's resident process (`lampson --daemon start`, or the open web UI),
38
- the run's session shows up as `⏰ name`, and a webhook can receive each result (for "search and send me" tasks).
39
- - **Approvals from anywhere**: an unattended run that hits something dangerous waits for you — in the web UI
40
- («Aprobaciones»), and, with `LAMPSON_PUBLIC_URL` + `LAMPSON_WEBHOOK_URL`, through a signed webhook carrying
41
- one-time **decision links** you can open from your phone (Telegram, mail, Slack…). Never auto-approved:
42
- no answer in time = denied.
43
- - **Project memory**: the agent keeps its own notes per project (`memory/<project>/*.md`, outside
44
- the repo) — how to run it, gotchas, decisions — and rereads them in the next session. You can read
45
- and edit them (web panel, `/memory`).
46
- - **`!command`**: run something yourself from the chat inside a real pseudo-terminal (prompts, passwords
47
- and REPLs work); the output lands in the agent's context.
48
- - **Terminal in the browser**: the web UI opens a real shell (pty, cwd = your project) in the center pane
49
- — `>_ terminal` in the header. Synsema 0.6.8+.
50
- - **Local only**: the web API (chat with tools, terminal, process control) answers loopback clients
51
- only; anything else gets 401. To let a script in from elsewhere, set `LAMPSON_WEB_TOKEN` in `.env`
52
- and send `Authorization: Bearer <token>`.
53
- - **Sessions** persisted as JSON, including every tool call, result, error and denial — plus a
54
- human-readable **trace** per session (`.lampson/trace/<id>.log`: one line per step, tool call,
55
- result, error, denial, with elapsed time and tokens) to see where the agent does well or badly:
56
- `/trace [n]` in the terminal, `≡` next to a session in the web UI, or `tail -f` the file.
7
+ ```
8
+ the login form doesn't show an error when the password is wrong. fix it
57
9
 
58
- ## Install
10
+ grep "password" src/ 3 files
11
+ ✓ read src/components/LoginForm.tsx 88 lines
12
+ ✓ edit src/components/LoginForm.tsx +6 −1
13
+ ? bash npm test allow? ❯ yes no
14
+ ✓ bash npm test 14 passed
59
15
 
60
- With npm (brings `synsema` along as a dependency):
16
+ The form now shows “Wrong email or password” when the API returns 401. Tests pass.
17
+ ─── 5 steps · 12k tokens · 38s
18
+ ```
19
+
20
+ ## Install
61
21
 
62
22
  ```sh
63
23
  npm i -g lampson
64
- cd /path/to/your/project
65
- lampson # terminal
66
- lampson --web # http://127.0.0.1:8080
67
24
  ```
68
25
 
69
- The package keeps your config, sessions, memory and global lamps in `~/lampson` (`LAMPSON_HOME` to change
70
- it) and refreshes the code there whenever you `npm i -g lampson@latest`. On Windows it needs PowerShell
71
- (pwsh or the built-in one) and Git for Windows (its bash is what the `bash` tool uses).
72
-
73
- Or the git-based installer — same result, `lampson --update` then means `git pull`. One line. It installs `synsema` if missing, puts Lampson in `~/lampson`, adds it to your PATH and
74
- asks for your provider + API key.
75
-
76
- **Windows (PowerShell)**
26
+ No Node.js? One line does it all (installs what's missing, including [Synsema](https://synsema.com)):
77
27
 
78
28
  ```powershell
29
+ # Windows (PowerShell)
79
30
  irm https://raw.githubusercontent.com/kitecosmic/lampson/main/install.ps1 | iex
80
31
  ```
81
32
 
82
- **Linux / macOS**
83
-
84
33
  ```bash
34
+ # macOS / Linux
85
35
  bash -c "$(curl -fsSL https://raw.githubusercontent.com/kitecosmic/lampson/main/install.sh)"
86
36
  ```
87
37
 
88
- Then, in a new terminal:
89
-
90
- ```bash
91
- cd /path/to/your/project
92
- lampson # terminal
93
- lampson --web # http://127.0.0.1:8080
94
- ```
95
-
96
- **First run**: Lampson asks for a provider, a model and its API key — a short wizard in the terminal, a
97
- welcome window in the web UI. That goes to `lampson/.lampson/config.json` (local, gitignored) and is
98
- shared by both UIs. Change it any time: `/setup` or `/provider <name> [model]` in the terminal, or click
99
- the `provider · model` pill in the web header (one key per provider is kept, so switching is instant).
100
- `.env` still works for the same settings (`LAMPSON_PROVIDER`, `LAMPSON_API_KEY`, `LAMPSON_API_KEY_<PROVIDER>`).
101
-
102
- **Images**: paste (Ctrl+V) or drop images into the web composer; they are downscaled in the browser
103
- (≤ 1568 px) and sent inline. In the terminal, `/paste` attaches the image on the clipboard (copy a
104
- screenshot first) and `/image <path>` a file; they go with your next message (`📎2 ❯`). If the current model does not declare image input, you are warned before
105
- sending and the model receives a text note instead of a 400. The table is `supports_vision` in
106
- `lib/provider.syn` (anthropic, minimax, gpt-4o/4.1/5, o-series, groq llama-4, and models named
107
- `*vision*`/`*vl*` on openrouter/ollama/kimi/grok); set `"vision": true` in `config.json` to override
108
- it for a model Lampson does not know — or `false` to force the text note.
109
-
110
- **Updating**: Lampson checks `origin/main` on start and tells you when a newer version exists (terminal
111
- banner, web header button). Run `lampson --update` (or `/update` in the REPL, or the web button) and
112
- restart. Re-running the installer does the same. `LAMPSON_HOME` changes the install folder.
113
- Requirements the installer handles for you: `synsema`, `git` (on Windows, Git for Windows also
114
- provides the bash the tools use — the installer offers to install it with winget). Linux: the
115
- `synsema` binary needs glibc ≥ 2.39 (Ubuntu 24.04+, Debian 13+, Fedora 40+).
116
-
117
- **Docker** (the `bash` tool is then confined by the container too)
118
-
119
- ```bash
120
- docker run --rm -p 8080:8080 -v "$PWD:/lampson/workspace" \
121
- -e LAMPSON_PROVIDER=deepseek -e LAMPSON_API_KEY=sk-... ghcr.io/kitecosmic/lampson
122
- ```
123
-
124
- Or from a clone: `docker build -t lampson .` and use `lampson` as the image. `docker-compose.yml`
125
- does the same with persistent sessions and memory.
126
-
127
- > Status: developed and tested on Windows 11; the Docker image (Ubuntu 24.04) is built by CI. The Linux/macOS
128
- > installer is written but not yet exercised on a real machine — issues welcome.
38
+ Then, in any project:
129
39
 
130
- ## Workspaces and the hub (how it runs)
131
-
132
- ```
133
- lampson # in a project folder: registers it as a workspace (first time), makes sure the hub and
134
- # the workspace's process are up, opens the terminal on it
135
- lampson --web # same, then opens http://127.0.0.1:8080/w/<slug> in the browser
136
- http://127.0.0.1:8080 # the workspaces screen: open one, create one (native folder dialog; a server-side
137
- # browser on a VPS), turn them on/off, life policy
138
- lampson --hub start|stop|status|logs|restart
139
- lampson --install # Windows: Scheduled Task at logon · Linux: systemd --user unit (Restart=always)
140
- ```
141
-
142
- Why processes: in Synsema a tool's file capability is a literal path relative to the process cwd, so a workspace is
143
- a directory `~/lampson/.lampson/ws/<slug>/` with a junction `workspace` → your project (plus junctions to the
144
- install's `lib/`, `public/`, `skills/`, `lamps/`, `memory/`), and its own `.lampson/` state. Its process
145
- (`synsema serve web.syn --port 808N --bind 127.0.0.1`) only ever sees that folder. The hub (`hub.syn`, generated
146
- from `hub.tpl.syn`, one proxy route per workspace) is the only listener you use: it serves the UI and forwards
147
- `/w/<slug>/api/…` — SSE and the WebSocket terminal included (Synsema ≥ 0.6.12). Workspace processes are detached
148
- from the hub, so a hub restart never kills them. The hub is also the supervisor: every 15 s it starts what should
149
- be alive and stops what is idle (⚙ → hours of inactivity, `0` = never; a workspace with enabled scheduled tasks or
150
- policy «siempre vivo» stays up).
151
-
152
- On a VPS put the hub behind your domain (`synsema serve hub.syn --port 443 --domain … --tls-auto …` or your
153
- edge); workspace ports never leave loopback. `LAMPSON_PUBLIC_URL` = `https://host` and approval links carry
154
- `/w/<slug>` automatically.
155
-
156
- ## Scheduled tasks and the resident process
157
-
158
- Lampson can run things with nobody at the keyboard. Scheduled tasks run inside the workspace's process (the hub
159
- keeps it alive while it has enabled tasks) — or inside the terminal REPL when no process is up (a background thread
160
- ticks every 30 s). `lampson --install` makes the hub survive reboots.
161
-
162
- Timezone, public URL and webhook are set from the ⚙ button of the web header (tabs: General · Aprobaciones a
163
- distancia · Proveedor; stored in `lampson/.lampson/config.json`, `.env` wins) or from `.env`.
164
-
165
- Create a task from the chat («todos los días a las 9 corré los tests y avisame» — the agent calls the
166
- `schedule` tool and asks you to approve the task, showing exactly what will run and with which permissions),
167
- from the web sidebar («Programadas» → +), or from the terminal (`/schedule add <json>`). Three kinds:
168
-
169
- | kind | what runs | authorization |
170
- |---|---|---|
171
- | `lamp` | a tool of a lamp that is ON | turning the lamp on |
172
- | `bash` | one fixed command (must finish on its own) | approved once, at creation |
173
- | `prompt` | a full agent turn with your instructions and a profile (`build` / `review` / `plan` / `explore`) | the permission envelope: `strict` (dangerous → denied), `ask` (dangerous → approval request, denied if unanswered within `approval_timeout`, 2 h by default), `yolo` |
174
-
175
- Schedules: `every 6h` · `every 30m` · `daily 09:00` · `mon,wed 08:30` · `weekdays 09:00` (local time; `LAMPSON_TZ`
176
- overrides). Synsema's `cron_every` is a pure interval, so a 30 s tick in `web.syn` translates it to wall-clock time;
177
- a run missed while the daemon was down executes when it comes back and is marked as late. State lives in
178
- `.lampson/schedules.json`, logs in `.lampson/schedules/<id>.log`; a `prompt` run also leaves a normal session (`⏰ name`)
179
- with its trace. `notify` = a webhook URL that receives each result as JSON.
180
-
181
- **Approving from your phone.** Set `LAMPSON_PUBLIC_URL` (how this Lampson is reached from outside — a tunnel, a VPS,
182
- or a Synsema edge with TLS in front) and `LAMPSON_WEBHOOK_URL` (+ `LAMPSON_WEBHOOK_SECRET`, HMAC-SHA256 in
183
- `X-Lampson-Signature`). Every pending approval — from a scheduled run or from the chat — POSTs `{id, message, why,
184
- expires_at, respond_link_yes, respond_link_no}`; forward the links wherever you read (n8n, a bot, a 6-line `.syn`).
185
- `GET /approve/<id>/<token>?d=yes|no` is public on purpose: the 32-byte one-time token is the authorization, and it
186
- dies with the deadline. `GET /api/approvals` (loopback) lists what is pending, never the tokens.
187
-
188
- For a real server use systemd (`synsema serve web.syn`, `Restart=always`) instead of `synsema daemon`, which has no
189
- boot start / crash restart. One box, several projects: one `web.syn` per project on its own port, behind a Synsema
190
- edge that terminates TLS and routes by host (see the Synsema deploy docs).
191
-
192
- ## Use
193
-
194
- ```bash
195
- cd /path/to/your/project
196
- lampson # terminal REPL; the current directory becomes the workspace
197
- lampson --web # web UI at http://127.0.0.1:8080
198
- lampson --agent plan # start in plan (read-only) mode
199
- lampson --yolo # never ask for dangerous commands (--strict: deny them; --ask: default)
200
- lampson --workspace /other # pick a project without cd
201
- lampson --help
40
+ ```sh
41
+ cd path/to/your/project
42
+ lampson # in the terminal
43
+ lampson --web # in the browser → http://127.0.0.1:8080
202
44
  ```
203
45
 
204
- In the REPL, `/` lists every command (`/agent`, `/ask` `/yolo` `/strict`, `/model`, `/config`,
205
- `/files`, `/procs`, `/logs <name>`, `/stop <name>`, `/kill <pid>`, `/skills`, `/sessions`,
206
- `/resume <id>`, `/tokens`, `/flags`). `!cmd` runs a command yourself.
207
-
208
- The prompt is a real line editor (Synsema ≥ 0.6.11, falls back to plain `read_line` without a TTY):
209
- typing `/` opens the command menu (recent ones first, filtered as you type), `Tab` completes the
210
- command and then its arguments (files for `/image`, sessions, providers, processes, lamps, MCP/LSP
211
- servers, flags), `↑↓` browse history or the menu, `Alt+Enter` inserts a newline, `Ctrl+O` shows the
212
- last tool result in full, `Ctrl+U`/`Ctrl+W` clear the line/word, `Esc` closes the menu. Approvals are
213
- an arrow-key menu (`permitir`/`denegar`, or `p`/`d`).
214
-
215
- While the model thinks or a slow tool runs (a long `bash`, a sub-agent), a status line shows what is
216
- running, the elapsed time and a bar that fills as you wait (`⠋ pensando ▰▰▱▱▱▱▱▱▱▱ 12s`); it appears
217
- after 0.4 s so instant tools do not flicker, and anything you type meanwhile lands in the prompt.
46
+ The first time it asks which provider you use DeepSeek, Anthropic, OpenAI, GLM (Z.ai), Kimi, Groq, OpenRouter,
47
+ Ollama (runs on your machine, no key)… and for your API key. That's it. Change it any time with `/model`.
218
48
 
219
- The terminal renders the model's markdown (headings, lists, tables, code fences) and shows every tool
220
- result: `edit`/`write` print a line diff (`- red / + green`, line numbers, 2 lines of context); other
221
- tools are collapsed to 15 lines. `/out [n]` prints the n-th last result of the turn in full and
222
- `/verbose` toggles full output for every tool (saved in `.lampson/config.json`).
49
+ More ways (Docker, from source, updating): **[lampson.org/install](https://lampson.org/install)**.
223
50
 
224
- The project is mounted as `lampson/workspace` (an NTFS junction on Windows, a symlink elsewhere)
225
- and every tool declares `file("workspace/*")` — that literal, named scope is what makes the
226
- confinement real. Config, sessions and process logs live in the `lampson` folder, never in your project.
51
+ ## What it's for
227
52
 
228
- ### Providers
53
+ - **Fix, build, refactor** — describe the change; it finds the files, edits them and runs your tests.
54
+ - **Understand a codebase** — `lampson --agent plan` is read-only: ask how things work, get a plan, nothing changes.
55
+ - **Review** — `/agent review` runs the tests and reads the diff, but never edits.
56
+ - **Run things while you're away** — "every weekday at 9, run the tests and tell me if something broke." If a
57
+ scheduled run hits something risky, you get a link on your phone to approve or deny it.
229
58
 
230
- | `LAMPSON_PROVIDER` | wire | endpoint |
231
- |---|---|---|
232
- | `openai` `deepseek` `kimi` `groq` `grok` `openrouter` `ollama` | OpenAI | `{base_url}/chat/completions` |
233
- | `anthropic` `minimax` | Anthropic | `{base_url}/messages` |
59
+ It can only touch the folder you opened it in — not your home directory, not the project next door. That isn't
60
+ a setting: it's how the language it's written in works. Reading and editing just happen; deleting, installing,
61
+ `git reset`, `sudo` stop and wait for your *yes*; truly destructive commands are refused in every mode.
234
62
 
235
- Any compatible endpoint: set `LAMPSON_BASE_URL` (and `LAMPSON_WIRE` if the preset can't guess).
236
- Model names are normalized to lowercase for providers whose APIs are case-sensitive (DeepSeek, OpenAI,
237
- Anthropic, Groq, Kimi, Grok, OpenRouter — `DeepSeek-V4-Pro` would be a 400); MiniMax and Ollama keep
238
- theirs. `/model` with no argument (or the model window in the web UI) lists what the provider's API
239
- actually offers (`GET /models`), so you never type a name blind.
240
- The API key is a Synsema `secret()`: the program can pass it as a header but never read or print it.
241
- See `.env.example` for every knob (steps, token budget, compaction threshold, shell).
63
+ ## Lamps
242
64
 
243
- ### Permissions
65
+ A **lamp** is a small folder that gives Lampson a new tool: query your database, call your company's API,
66
+ deploy, send a message. Any language. **Off by default** — you turn each one on, and only then can the agent
67
+ use it.
244
68
 
245
- | level | examples | ask | yolo | strict |
246
- |---|---|---|---|---|
247
- | hardline | `rm -rf /`, formatting disks, fork bombs, force-push to main | blocked | blocked | blocked |
248
- | dangerous | recursive deletes, `git reset --hard`, `sudo`, `curl \| sh`, `DROP TABLE`, writing `.env` | **asks you** | allowed | blocked |
249
- | everything else | read, edit, tests, `git status`… | allowed | allowed | allowed |
250
-
251
- Change the mode with the web selector, `/ask` `/yolo` `/strict`, or the launch flags — `.env` only
252
- sets the default. The `bash` child process itself is not confined by Synsema; for a public deployment,
253
- run Lampson in a container.
254
-
255
- ## Architecture
256
-
257
- ```
258
- lampson.ps1 / .sh launcher: resolves the workspace (cli.syn), ensures hub + process, opens terminal or browser
259
- cli.syn what launchers ask Synsema: ensure/list/hub-start/hub-stop/hub-restart (last line = JSON)
260
- hub.tpl.syn template of the hub (:8080): workspaces screen + API, supervisor tick, one proxy route per workspace
261
- lib/workspaces.syn registry (.lampson/workspaces.json), ws/<slug> dirs + junctions, detached processes, health,
262
- life policy, folder picker (native dialog / server-side browser)
263
- chat.syn terminal REPL (colors, approvals via Synsema's native `approve`)
264
- web.syn one process per workspace: /w/:slug/api/… → SSE chat, sessions, tree, processes, schedules…
265
- public/ web UI (no build step, no dependencies; classic scripts served by `static`)
266
- index.html markup only: header, the two side panels, the chat; loads css/ and js/ in order
267
- css/ tokens (fonts, palette, base) · layout (grid, header, panels, chat, composer) · sidebar · chat · panel
268
- js/core.js shared state + helpers ($, esc, md, add, api, showPane/showText, inlineConfirm, debounce, empty)
269
- js/panel.js THE modal component: one shell, three layouts (browse = search + list + detail, tabs, form)
270
- js/<view>.js one file per thing on screen: sessions, chat, tree, terminal, procs, agents, memory, todo,
271
- mcp, lsp, lamps, schedules, approvals, config (+ provider), update, events (SSE), app (boot)
272
- lib/
273
- provider.syn config from .env · chat(cfg, messages, catalog) · retry with backoff
274
- loop.syn run_turn(): LLM → tool calls → permissions → call_tool → results → repeat; doom-loop guard; compaction
275
- tools.syn tool registry (allow-list) + JSON-Schema catalog + per-profile subsets
276
- tools/<x>.syn one tool per file: read write edit ls find grep bash process skill
277
- tools/common.sh shared shell helpers (kill process trees on Windows/unix)
278
- tools/proc.sh ports, command line of a pid, kill a foreign pid tree (the managed processes are native)
279
- agents.syn profiles (build / plan / review / explore / worker) + `delegate` (sub-agents: batch, background, inbox, steer/stop)
280
- permission.syn evaluate(tool, args, mode) → allow | deny | ask
281
- prompt.syn system prompt (rules, tools, environment, skills index, AGENTS.md of the project)
282
- skills.syn SKILL.md index (harness / project / local)
283
- session.syn .lampson/sessions/<id>.json
284
- tree.syn · git.syn workspace tree, git status
285
- skills/ built-in skills: lampson (how the harness works), synsema, debugging
286
- tests/mock_llm.syn scripted fake LLM (both wires) for end-to-end runs without an API key
287
- unit.test.syn unit tests — run them with tests/run.ps1 · tests/run.sh (throwaway workspace)
288
- ```
289
-
290
- ### The loop
291
-
292
- ```
293
- while steps < max_steps and tokens <= budget
294
- r = provider.chat(cfg, messages, catalog) -- last step: no tools → forces a final answer
295
- messages += assistant(r.text, r.tool_calls)
296
- if no tool_calls → return
297
- for each call:
298
- doom loop (3× identical) → error back to the model
299
- 50% of the token budget → nudge (stop exploring, act); 90% → final answer without tools
300
- permission.evaluate → deny | ask | allow
301
- out = call_tool(registry[name], args) -- least-privilege; errors go back as text
302
- messages += tool(id, out) -- everything is kept, including failures
303
69
  ```
304
-
305
- ### MCP servers
306
-
307
- `lib/mcp.syn` is a stdio MCP client. Config, in the format you already have elsewhere:
308
-
309
- ```json
310
- {"mcpServers": {"github": {"command": "npx", "args": ["-y", "@modelcontextprotocol/server-github"],
311
- "env": {"GITHUB_TOKEN": "…"}}}}
70
+ ~/lampson/lamps/postgres/
71
+ lamp.json ← what it's called, what it does, which tools it offers
72
+ query.py ← the code
312
73
  ```
313
74
 
314
- - `lampson/.lampson/mcp.json` is **global**: lampson is installed once, so a server declared there is
315
- available in every repo you open. `workspace/.lampson/mcp.json` is per project (a server with the same
316
- name overrides the global one). `"disabled": true` keeps an entry without starting it; `"cwd"` defaults
317
- to the workspace.
318
- - Each server runs inside a supervisor `agent` (like managed processes): `initialize` → `tools/list`,
319
- then requests travel over the bus (`mcp.req.<server>` / `mcp.res.<server>.<id>`). State on the
320
- blackboard (`mcp:<server>`), status in `/mcp` and the web sidebar; a server that dies shows `error`.
321
- - Tools appear to the model as `mcp_<server>_<tool>` with the server's JSON Schema. `build`/`worker` see
322
- all of them; `plan`/`review`/`explore` only those marked `readOnlyHint`. Every call goes through
323
- `permission.syn`: **ask** by default, allow in yolo, deny in strict — and lands in the session trace.
324
- - Not yet: HTTP/SSE transports, resources/prompts, sampling.
325
-
326
- ### LSP (language servers)
327
-
328
- `lib/lsp.syn`, same shape as the MCP client (deepseek-harness `packages/lsp` was the reference: the harness
329
- installs nothing, the user declares a server per language, it starts lazily, and the model gets a closed
330
- set of operations — no raw JSON-RPC). Config:
331
-
332
75
  ```json
333
- {"servers": {"typescript": {"command": "npx", "args": ["--yes", "typescript-language-server", "--stdio"],
334
- "languages": {".ts": "typescript", ".tsx": "typescriptreact"}}}}
76
+ {"name": "postgres", "description": "read-only queries on the dev database",
77
+ "kind": "exec", "command": "python query.py",
78
+ "tools": [{"name": "query", "description": "Run a SELECT, return rows as JSON",
79
+ "parameters": {"type": "object", "properties": {"sql": {"type": "string"}}}, "readonly": true}]}
335
80
  ```
336
81
 
337
- - `lampson/.lampson/lsp.json` is global, `workspace/.lampson/lsp.json` per project. Presets (`/lsp add
338
- <name>`, or the sidebar): typescript, python (pyright), rust (rust-analyzer), go (gopls), css, html.
339
- `cwd` defaults to the workspace; `env` and `"disabled": true` as in MCP. On Windows a bare `npx`/`npm`
340
- command is retried as `.cmd`. The typescript preset needs `typescript` in the project's `node_modules`
341
- (any TS project has it) — verified against a real `typescript-language-server` via `npx`.
342
- - One supervisor `agent` per server; stdio with `Content-Length` framing (`line_mode: false`, byte-exact
343
- cut); server→client requests (`workspace/configuration`, `client/registerCapability`…) are answered with
344
- `null`, notifications (diagnostics) ignored. `rootUri` is the real project path (`LAMPSON_WORKSPACE`),
345
- results come back relative to it (case-insensitive on Windows, `%3A` decoded).
346
- - Tool `lsp(op, path, line, character)` — `symbols` (hierarchical outline with line ranges), `definition`,
347
- `references` (declaration included), `implementation`, `hover`. Each query opens the document
348
- transiently (`didOpen` → request → `didClose`), positions are 1-based like the editor. Read-only: allowed
349
- in every mode and profile. Test: `lsp_test.syn` against `tests/mock_lsp.js`.
350
-
351
- ### Lamps (tool plugins)
352
-
353
- `lib/lamps.syn`. Synsema has no dynamic `use` (on purpose: no supply chain inside the process), so a lamp
354
- never loads into lampson — every call is **one child process that ends**, the "safe runner" pattern from
355
- the Synsema sandbox docs. `lamps/<name>/lamp.json`:
356
-
357
- ```json
358
- {"name": "hello", "description": "greets", "kind": "syn", "entry": "lamp.syn",
359
- "caps": "file.read=workspace/*", "timeout": 60,
360
- "tools": [{"name": "greet", "description": "…", "parameters": {"type": "object", "properties": {"who": {"type": "string"}}}, "readonly": true}]}
361
- ```
82
+ The script gets the call in environment variables (`LAMP_TOOL`, `LAMP_ARGS` as JSON) and prints the result.
83
+ Turn it on with `/lamps on postgres` or the **lámparas** pill in the web UI. Keep lamps global (every project)
84
+ or inside a project (`.lampson/lamps/` commit it and your team has it). The agent can *write* a lamp for you;
85
+ turning it on is always yours.
362
86
 
363
- - `kind: "syn"` runs `synsema run --cap-set stdout,time,env=LAMP_*[,caps] <entry>`: the ceiling is the
364
- manifest you approved when you turned it on; a `require` above it in the lamp's code fails with
365
- *above the host ceiling* — nothing escalates from inside. `kind: "exec"` runs `"command"` as is (no
366
- language ceiling — which is why enabling is always a human decision).
367
- - The tool call travels by env: `LAMP_TOOL`, `LAMP_ARGS` (JSON), `LAMP_DIR`, `LAMP_WORKSPACE`; the lamp
368
- prints its result to stdout. Exit ≠ 0 or timeout → `ERROR:` for the model.
369
- - Discovery: `lamps/` (global) and `workspace/.lampson/lamps/` (project; same name overrides). State in
370
- `.lampson/lamps.json` — **off by default**. On/off: web top bar, `/lamps on|off <name>`, or the model's
371
- `lamp(action=enable)` which always asks. Lamp tools: ask by default, allow in yolo, deny in strict;
372
- `readonly` ones also reach `plan`/`review`/`explore`. Example: `lamps/example-hello/`.
373
- - The agent can build lamps itself: `lamp(action=create, name, manifest, files)` writes a project lamp,
374
- validates the manifest and runs `synsema check` on a syn entry — define-and-validate only, like dsh's
375
- `cordis_define`; turning it on is still yours (`cordis_run`'s approval, here `enable` → ask).
376
-
377
- ### Keeping the model aware of what it did
378
-
379
- Borrowed from the harness that does each part best (see `notes/*.md`):
380
-
381
- | Problem | Mechanism | From |
382
- |---|---|---|
383
- | A huge tool result floods the context | **Spill**: results over 10k chars are saved whole to `.lampson/spill/<call>.txt`; the model sees head + tail + the path (`read` is exempt, it pages) | deepseek |
384
- | Old results are re-sent with every call | **Receipts**: before each model call, tool results beyond the last 40k tokens become one line — `[bash result pruned: exit code 0 · 412 lines/18k chars · Full result saved to …]` | hermes / opencode |
385
- | Same call, same args, again and again | **Repeat reminder**: 3rd and 5th identical call (canonical JSON) still run but carry a reminder; the 8th is refused | deepseek / opencode |
386
- | Budget runs out silently | 80 %: a notice appended to the latest tool result (no new user message, cache stays warm); 95 %: last step without tools, summary required | hermes / opencode |
387
- | Edits a file it never read, or one that changed | **Observation gate in code**: `read` records the file hash; `edit`/`write` on an existing file are rejected without it, or if the file changed since | deepseek |
388
- | Loses the plan | `todo` tool (whole-list replacement, one `in_progress` at a time, scoped to the session like the three references); re-injected only after context compaction, active items only | hermes, opencode |
389
- | Reads the whole project before touching anything | **Exploration cap** (ours): after 12 read-only calls in a row (read/ls/find/grep) without an edit/write/command the result carries a warning; after 24 they are refused until it acts (`LAMPSON_EXPLORE_CAP`) | — |
390
-
391
- ### Sub-agents
392
-
393
- `delegate(tasks=[{agent, brief, context}…], background?)` — or `action=list|steer|stop|result`.
394
-
395
- - **Batch, in parallel**: each task runs `loop.run_turn` in its own thread (`parallel_map`, 4 at a time,
396
- max 6 per call) with a fresh history, the profile's toolset and *no* `delegate` (depth 1). The parent
397
- gets one consolidated report; every child writes `.lampson/agents/<id>.log` (tail it) and `<id>.json`.
398
- - **Background**: returns the ids at once; the child runs inside a Synsema `agent` (own interpreter) and
399
- its report enters the parent's **inbox**: `loop.run_turn` checks `opts.inbox_fn` before every model call
400
- and appends pending reports as new `user` messages (never mutating past context — prefix cache intact);
401
- in the terminal an idle parent gets an automatic turn (max 3 in a row, reset by real input).
402
- `steer` = text the child reads before its next step; `stop` = cut and return the partial report.
403
- - **Permissions**: a child never asks the user — it runs `strict` (dangerous → denied with the reason;
404
- it reports the limitation) or `yolo` if `LAMPSON_PERMISSION=yolo`. Reports are self-reports: the
405
- prompt tells the parent to verify (read the file, run the test) before claiming success.
406
- - **History**: finished sub-agents are pruned (older than 30 min, or beyond the last 10) whenever a new one
407
- is delegated or the list is shown; the test suite cleans its own.
408
- - **Live UI**: `GET /api/events` is an SSE stream fed by the Synsema bus — process supervisors publish every
409
- log line and status change (`proc.<name>`), sub-agents their start/steps/end (`subagent.*`) — so the web
410
- panels and open logs refresh on events, not on timers (timers stay only as a slow fallback).
411
- - Runtime detail: a Synsema `agent` only sees its spawn parameters, the builtins and the *top-level* tasks
412
- of the entry program, so `chat.syn`/`web.syn` define `task lampson_subagent(spec)` as the child's door.
413
-
414
- ### Adding a tool
415
-
416
- `lib/tools/<name>.syn` exporting `task tool(...)` (its `require` lines at the top of the body) and
417
- `let SPEC` (JSON Schema); register it in `lib/tools.syn` and in the profiles of `lib/agents.syn`.
418
- `synsema check chat.syn` validates the whole graph. There is deliberately no dynamic loading: the
419
- allow-list is a file you can read.
420
-
421
- ### Skills
422
-
423
- A folder with `SKILL.md` (`name:` + `description:` front matter) — the [Agent Skills](https://agentskills.io)
424
- format, so anything published on [skills.sh](https://skills.sh) works as-is. The system prompt carries only
425
- the index; the content enters the context when the model calls `skill(name)`. Search order (later wins):
426
- `~/.agents/skills`, `~/.claude/skills` (global) < `skills/` (harness) < `.claude/skills`, `.agents/skills`,
427
- `skills/` of the project < `.lampson/skills/` of the project (local, not committed).
428
-
429
- **Installing external skills** — the agent can do it for you: ask *"install the frontend-design skill from
430
- anthropics/skills"* and it calls `skill(action=install, source, name, scope=global|project)`, which runs the
431
- standard installer below. Installing is **always human-in-the-loop** (it asks even in yolo mode; strict denies
432
- it) because it brings third-party instructions and scripts onto your machine. Global (default) means
433
- `~/.agents/skills`: a Go skill installed once serves every Go project. Or do it yourself:
434
-
435
- ```bash
436
- npx skills add anthropics/skills --skill frontend-design # → ./.agents/skills (this project)
437
- npx skills add anthropics/skills --skill frontend-design -g # → ~/.agents/skills (every project)
438
- npx skills list · npx skills update · npx skills remove
439
- ```
440
-
441
- The home folders are mounted as junctions/symlinks under `.lampson/skills-global` and
442
- `.lampson/skills-claude` by `lampson.ps1` / `lampson.sh` (a capability cannot point at a dynamic path).
443
-
444
- ### Testing without an API key
445
-
446
- ```bash
447
- synsema serve tests/mock_llm.syn
448
- LAMPSON_PROVIDER=openai LAMPSON_WIRE=openai LAMPSON_BASE_URL=http://127.0.0.1:8765/v1 LAMPSON_API_KEY=x lampson
449
- tests/run.sh # Windows: .\tests\run.ps1
450
- ```
87
+ [How lamps work](https://lampson.org/docs/lamps) · the example lamp ships in `lamps/example-hello/`.
451
88
 
452
- The unit tests write into `workspace/`, so the runner mounts a temporary folder for them and
453
- restores your project afterwards; `unit.test.syn` refuses to run on anything else.
89
+ ## Also in the box
454
90
 
455
- ## Synsema runtime notes (v0.6.7)
91
+ Sub-agents that work in parallel · skills (`SKILL.md` procedures, anything on [skills.sh](https://skills.sh)
92
+ works) · MCP servers with the JSON you already have · language servers for real go-to-definition · project
93
+ memory it reads back next session · sessions with a readable trace of every step · paste a screenshot and ask.
456
94
 
457
- Things that cost time and are handled in the code:
95
+ ## Learn more
458
96
 
459
- - `file("./*")` behaves like `"*"` (whole disk); `file("dir/*")` confines — hence the `workspace` mount.
460
- - `http_post` with a map body sends `text(map)`, not JSON always `json_encode` + `Content-Type`.
461
- - Responses expose `status, ok, body, headers` (no `json`); on network error `status 0` + `error`.
462
- - `number()` returns floats → `floor()` for every integer that goes on the wire.
463
- - `run()` hangs forever if the command leaves a descendant holding stdout (Windows inherits handles)
464
- and its timeout only kills the direct child → the `bash` tool and the `process` tool use `proc_spawn`
465
- (v0.6.9): events per line, real deadline, `proc_close` kills the whole tree (Job Object / process group).
466
- - Under `serve`, a `proc_spawn` made in a handler dies with the request. A managed process therefore lives
467
- inside an `agent` (own lifecycle: verified under `run` and `serve`): state on the blackboard
468
- (`proc:<name>`), stop via `bus_publish("proc.stop.<name>")`, output drained to `.lampson/proc/<name>.log`.
469
- Processes die with lampson (`/exit` publishes `proc.stop_all`; Ctrl-C under serve → `agent_stop`).
470
- - Git Bash (MSYS) has two process trees: `bash -c "a && npm run dev"` execs its last command, so the
471
- MSYS pid shows as `cmd`/`node` (never trust the name to decide liveness — compare the WINPID), and native
472
- grandchildren (`npm.cmd → cmd → node`) are invisible to MSYS `ps` → `kill_tree` (foreign pids only) does
473
- `taskkill /T /F` per WINPID. The managed log ends with `[process exited with code N]`; `bash` refuses
474
- server-looking commands and points to `process`.
475
- - `localhost` may resolve to IPv6 while `serve` listens on IPv4 → use `127.0.0.1`.
476
- - Reserved words that bite: `reason task ask stop decide analyze generate show approve confirm`.
477
- - `and`/`or` short-circuit only from Synsema v0.6.10 (before, `contains(m, "k") and m["k"]` exploded — the code still nests `when` for that); a task named `run` shadows the builtin (and a module task named `read`/`write` breaks `read_file`/`write_file` calls in sibling tasks); modules cannot import `../`; `split(s, "")` is an error; `slice` past the end errors.
97
+ - **[lampson.org/docs](https://lampson.org/docs)** quickstart, permissions, providers, lamps, schedules…
98
+ - **[guide.md](guide.md)** the long version: how it runs, architecture, every knob, runtime notes.
478
99
 
479
100
  ## License
480
101