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