lampson 0.1.0

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.
Files changed (62) hide show
  1. package/.env.example +29 -0
  2. package/LICENSE +21 -0
  3. package/README.md +382 -0
  4. package/bin/lampson.js +81 -0
  5. package/chat.syn +799 -0
  6. package/lamps/example-hello/lamp.json +16 -0
  7. package/lamps/example-hello/lamp.syn +19 -0
  8. package/lampson.cmd +4 -0
  9. package/lampson.ps1 +88 -0
  10. package/lampson.sh +42 -0
  11. package/lib/agents.syn +471 -0
  12. package/lib/git.syn +58 -0
  13. package/lib/lamps.syn +386 -0
  14. package/lib/loop.syn +455 -0
  15. package/lib/lsp.syn +503 -0
  16. package/lib/mcp.syn +403 -0
  17. package/lib/permission.syn +154 -0
  18. package/lib/prompt.syn +75 -0
  19. package/lib/provider.syn +522 -0
  20. package/lib/session.syn +111 -0
  21. package/lib/settings.syn +70 -0
  22. package/lib/skills.syn +179 -0
  23. package/lib/tools/bash.syn +105 -0
  24. package/lib/tools/common.sh +49 -0
  25. package/lib/tools/common.syn +91 -0
  26. package/lib/tools/edit.syn +32 -0
  27. package/lib/tools/find.syn +35 -0
  28. package/lib/tools/grep.syn +31 -0
  29. package/lib/tools/img.ps1 +36 -0
  30. package/lib/tools/img.sh +22 -0
  31. package/lib/tools/ls.syn +18 -0
  32. package/lib/tools/memo.syn +148 -0
  33. package/lib/tools/proc.sh +42 -0
  34. package/lib/tools/proc.syn +314 -0
  35. package/lib/tools/process.syn +46 -0
  36. package/lib/tools/read.syn +25 -0
  37. package/lib/tools/skill.syn +14 -0
  38. package/lib/tools/todo.syn +97 -0
  39. package/lib/tools/write.syn +22 -0
  40. package/lib/tools.syn +198 -0
  41. package/lib/trace.syn +116 -0
  42. package/lib/tree.syn +59 -0
  43. package/lib/update.syn +57 -0
  44. package/package.json +40 -0
  45. package/public/fonts/plex-mono-400-latin-ext.woff2 +0 -0
  46. package/public/fonts/plex-mono-400-latin.woff2 +0 -0
  47. package/public/fonts/plex-mono-600-latin-ext.woff2 +0 -0
  48. package/public/fonts/plex-mono-600-latin.woff2 +0 -0
  49. package/public/fonts/plex-serif-400-latin-ext.woff2 +0 -0
  50. package/public/fonts/plex-serif-400-latin.woff2 +0 -0
  51. package/public/fonts/plex-serif-400i-latin-ext.woff2 +0 -0
  52. package/public/fonts/plex-serif-400i-latin.woff2 +0 -0
  53. package/public/fonts/plex-serif-600-latin-ext.woff2 +0 -0
  54. package/public/fonts/plex-serif-600-latin.woff2 +0 -0
  55. package/public/index.html +1268 -0
  56. package/public/vendor/xterm-addon-fit.js +2 -0
  57. package/public/vendor/xterm.css +218 -0
  58. package/public/vendor/xterm.js +2 -0
  59. package/skills/debugging/SKILL.md +33 -0
  60. package/skills/lampson/SKILL.md +117 -0
  61. package/skills/synsema/SKILL.md +75 -0
  62. package/web.syn +468 -0
@@ -0,0 +1,33 @@
1
+ ---
2
+ name: debugging
3
+ description: How to write and run throwaway scripts (sh, js/mjs/ts, py…) in the scratch dir to test, reproduce or probe something in the project — before touching real code. Load when a task needs an experiment, a repro, or a quick check of an API/library/function.
4
+ ---
5
+
6
+ # Debugging with scratch scripts
7
+
8
+ You have a scratch directory inside the workspace: **`.lampson/scratch/`** (create it with `write`;
9
+ it is ignored by lampson's file tree and must never be committed — add `.lampson/` to the project's
10
+ `.gitignore` if it is a git repo and it is not there yet).
11
+
12
+ Use it to *prove* things instead of guessing:
13
+
14
+ - **Reproduce a bug** in isolation: `write .lampson/scratch/repro.mjs` that imports the real module
15
+ (`import { f } from '../../src/lib/x.js'`) and calls it with the failing input; run with
16
+ `bash: node .lampson/scratch/repro.mjs`. Paths in the script are relative to the workspace root
17
+ because `bash` runs there.
18
+ - **Probe an API or a running server**: `curl -s -i http://127.0.0.1:3000/api/x` (start the server with
19
+ the `process` tool first, then read its log with `process(logs)` after the request).
20
+ - **Check a library's behavior** before relying on it: a 5-line script beats reading docs from memory.
21
+ - **Try a data transformation** on real data: dump a sample to `.lampson/scratch/sample.json`, iterate.
22
+ - **Run one test in isolation** (`npx vitest run path -t "name"`, `pytest path::test -x`, etc.).
23
+
24
+ Pick the runtime the project already uses (look at `package.json`, `pyproject.toml`, `go.mod`,
25
+ `Cargo.toml`…): `node x.mjs` / `npx tsx x.ts` / `python x.py` / `bash x.sh` / `go run x.go` /
26
+ `synsema run x.syn`. TypeScript: prefer `npx tsx` (no build step); if it is not installed, write `.mjs`.
27
+
28
+ Rules:
29
+ 1. Scripts print **evidence** (values, status codes, timings) — not just "ok". Compare expected vs actual.
30
+ 2. Keep them small and single-purpose; name them by intent (`repro-login-401.mjs`, `probe-db.py`).
31
+ 3. Never import secrets into a script; read config the same way the project does.
32
+ 4. When done, say what the script proved. Delete scripts that are pure noise; keep repros that document a bug.
33
+ 5. Anything long-running (a server you start to probe) goes through the `process` tool, never in the foreground.
@@ -0,0 +1,117 @@
1
+ ---
2
+ name: lampson
3
+ description: How this harness works — tools, workspace mount, permissions, agents, sessions. Load when asked about Lampson itself or when a tool behaves unexpectedly.
4
+ ---
5
+
6
+ # Lampson (the harness you are running in)
7
+
8
+ ## Where you are
9
+ - Your tools only reach `workspace/` — the user's project, mounted as a junction/symlink by
10
+ `lampson.ps1 <path>` / `lampson.sh <path>`. All paths you pass are relative to that root.
11
+ - Anything outside (absolute paths, `..`, sibling dirs) fails with `Capability not granted`.
12
+ That is by design (Synsema deny-by-default + `call_tool` least-privilege). Do not try to work
13
+ around it; tell the user if you genuinely need something outside the workspace.
14
+ - `bash` runs with cwd = workspace root and a hard timeout (default 120 s): when it expires the command AND
15
+ its child processes are killed. Never run servers/watchers/REPLs in `bash`; use the `process` tool:
16
+ `process(start, name="web", command="npm run dev")`, then `process(logs, name="web")`, and
17
+ `process(stop, name="web")` when done. New log lines of every managed process are appended to each
18
+ `bash` result automatically — you SEE the server's console (errors, requests, crashes) without asking.
19
+ The user sees the same logs live in the web UI ("Procesos" panel). Its child process is NOT confined by Synsema; dangerous
20
+ commands are screened by `permission.syn` (hardline = always denied; dangerous = needs the user's
21
+ approval in `ask` mode, denied in `strict`, allowed in `yolo`).
22
+
23
+ ## Tool behavior worth knowing
24
+ - `edit` needs an exact, unique `old_string`; if it fails, `read` the region again and retry with
25
+ more context. `replace_all=true` for intentional multi-replace.
26
+ - `read` returns up to 2000 lines; use `offset`/`limit` for big files. Outputs > 30k chars are truncated.
27
+ - `find` is a simple glob (`*.ts`, `test_*`, `*config*`), `grep` is regex by default (`regex=false` for literals).
28
+ - `lsp(op=symbols, path)` is the cheapest way to understand a big file: every function/class/variable
29
+ with its line range — then `read` only the range you need. `definition` / `references` / `hover` /
30
+ `implementation` take 1-based `line` + `character` ON the identifier. If no server is configured for
31
+ that extension, propose `lsp(op=add, server=<preset>)` (typescript, python, rust, go, css, html) — it
32
+ always asks the user and needs nothing installed (`npx` fetches it); `op=list` shows what is configured.
33
+ Do not install language servers with bash yourself.
34
+ - Calling the same tool with identical args 3 times in a row is blocked (doom loop) — change approach.
35
+ - After 8 tool errors in a turn the harness asks you to stop and report.
36
+ - `delegate(tasks=[{agent, brief, context}…])` runs sub-agents (`explore` / `plan` / `review` /
37
+ `worker`) IN PARALLEL, each with a fresh context and a restricted toolset, and returns their reports
38
+ consolidated. `background=true` returns ids at once and each report arrives later as a message in
39
+ your context (do not poll); `action=list|steer|stop|result` manages them. Give a self-contained
40
+ brief — a child does not see this conversation, cannot ask the user and cannot delegate. Reports are
41
+ self-reports: verify before claiming success. Live logs: `.lampson/agents/<id>.log`.
42
+
43
+ ## When you need the USER to run something
44
+ The user can run any command themselves from the chat by prefixing it with `!` — e.g. `!npm run dev`,
45
+ `!cat .env`, `!git push`. Their command runs without the permission policy and its output is added to
46
+ your context automatically. In the terminal REPL, `!` runs inside a real pseudo-terminal: prompts
47
+ (`y/N`, passwords, `npm init`) work — the user answers them inline. In the web UI there is also a
48
+ **full interactive terminal** (header button `>_ terminal`, a shell with cwd = workspace, opens in the
49
+ center pane like a file). Offer these when: a command needs an interactive terminal (logins, TUIs,
50
+ watching a dev server), when something is denied for you (secrets, dangerous commands), or when they
51
+ should verify a result with their own eyes. Say exactly what to type, e.g. "run `!npm run dev` (or open
52
+ the terminal button and run `npm run dev`), then tell me the URL".
53
+
54
+ ## Provider, model, keys, images
55
+ - Provider/model/API keys live in `lampson/.lampson/config.json` (local); the user changes them with
56
+ `/setup`, `/provider`, or the `provider · model` pill in the web header. Never ask the user to paste a
57
+ key into the chat; point them there. Keys are sealed secrets: you cannot read or print them.
58
+ - The user can paste images in the web UI, or in the terminal with `/paste` (clipboard) and
59
+ `/image <path>`. If they say "look at this screenshot" in the terminal, tell them to copy it and type
60
+ `/paste`, then send their message. If your model has no vision, the image arrives as a text
61
+ note `[Image N WxHpx attached, but this model does not accept image input …]` — say so and suggest
62
+ a vision model instead of guessing what the image shows.
63
+ - Sessions can be deleted (`/delete <id>`, or ✕ in the web sidebar).
64
+
65
+ ## Network exposure
66
+ - The web server listens on all interfaces but every `/api/*` route (and the terminal socket) only
67
+ accepts loopback clients; others get 401 unless they present `LAMPSON_WEB_TOKEN`. If the user asks
68
+ to use Lampson from another machine, point them to that token — never suggest removing the check.
69
+
70
+ ## Agents / modes
71
+ - `build` (default): all tools. `plan`: read-only, produce a numbered plan. `review`: read + run
72
+ tests, never edit. `explore`: read-only search. The user switches with `/agent <name>`.
73
+
74
+ ## Project memory (persistent notes)
75
+ - `memory(write, name, content)` saves a Markdown note about THIS project in Lampson's `memory/<project>/`
76
+ folder (outside the repo). The system prompt lists your notes; `memory(read, name)` loads one.
77
+ - Save what you would otherwise rediscover: how to run/test, env quirks, decisions, where things live,
78
+ root causes of bugs. Update notes instead of contradicting them. The user can read and edit them.
79
+
80
+ ## Sessions and memory
81
+ - The whole history — including every tool result, error and DENIED message — is persisted in
82
+ `.lampson/sessions/<id>.json` and resent to you each turn, so you know exactly what happened.
83
+ - When the context grows past the compaction threshold, older turns are replaced by a summary that
84
+ keeps goals, files touched, current state, pending work and errors seen.
85
+
86
+ ## Updating Lampson
87
+ - Lampson is installed as a git clone (`~/lampson`), so updating is a fast-forward pull. Both UIs
88
+ check `origin/main` on start and show a notice when a newer version exists (terminal banner, web
89
+ header button). The user updates with **`lampson --update`** in any terminal, `/update` in the REPL,
90
+ or the header button in the web UI — then restarts Lampson. Re-running the installer does the same.
91
+ - If asked "how do I update Lampson?", answer exactly that. If the pull fails because of local edits
92
+ in the lampson folder, suggest `git -C ~/lampson stash` first. (When the distribution changes —
93
+ desktop app, single binary — this section is the place that gets rewritten.)
94
+
95
+ ## Extending the harness (if the user asks you to)
96
+ - One tool = one file `lib/tools/<name>.syn` exporting `task tool(...)` (its `require` lines at the
97
+ top of the body) and `let SPEC` (JSON Schema). Register it in `lib/tools.syn` (`use`, `registry()`,
98
+ `CATALOG`) and in the profiles of `lib/agents.syn`. `synsema check chat.syn` validates everything.
99
+ - Providers are raw HTTP (`lib/provider.syn`): OpenAI wire (`/chat/completions`) and Anthropic wire
100
+ (`/messages`). Config via `.env` (`LAMPSON_PROVIDER`, `LAMPSON_API_KEY`, `LAMPSON_MODEL`, `LAMPSON_BASE_URL`).
101
+ - Skills: `skills/<name>/SKILL.md` (harness), `workspace/skills/<name>/SKILL.md` (project),
102
+ `workspace/.lampson/skills/<name>/SKILL.md` (local), plus external ones installed with `npx skills add`
103
+ (`workspace/.agents/skills`, `workspace/.claude/skills`, and the global `~/.agents/skills` / `~/.claude/skills`
104
+ mounted as `.lampson/skills-global` / `.lampson/skills-claude`). Frontmatter `name:` + `description:`.
105
+ `skill(action=install, source=owner/repo, name=x, scope=global|project)` installs one (always asks the user).
106
+ - **Lamps** = tool plugins you can create for this project without touching the harness:
107
+ `lamp(action=create, name, manifest, files={"lamp.syn": "…"})` writes `workspace/.lampson/lamps/<name>/`,
108
+ validates the manifest and runs `synsema check` on a syn entry (it does not run or enable anything —
109
+ like dsh's cordis_define). Then `lamp(action=enable, name)` — the user must approve (a lamp is off until
110
+ a human turns it on). Build one when a task needs a reusable project-specific tool. Manifest:
111
+ `{"name", "description", "kind": "syn"|"exec", "entry": "lamp.syn" (syn) | "command": "python lamp.py" (exec),
112
+ "caps": "file.read=workspace/*" (syn, optional extra ceiling over stdout,time,env=LAMP_*), "timeout": 60,
113
+ "tools": [{"name", "description", "parameters": {JSON Schema}, "readonly": bool}]}`. Lamp names: letters,
114
+ digits, `-` (no `_`). Each call runs the lamp as ONE child process: `synsema run --cap-set <ceiling> entry`
115
+ for `syn`, the command for `exec`. Inside, read `LAMP_TOOL` and `LAMP_ARGS` (JSON) from env
116
+ (`require env("LAMP_*")` in a .syn) and print the result to stdout. A syn lamp cannot use more than its
117
+ manifest's `caps`; ask only for what the tool needs. Once on, its tools are `lamp_<name>_<tool>`.
@@ -0,0 +1,75 @@
1
+ ---
2
+ name: synsema
3
+ description: Writing, checking, running and testing Synsema (.syn) code — syntax reflexes, capabilities, live processes / pseudo-terminals, and the runtime traps that cost hours. Load before touching any .syn file.
4
+ ---
5
+
6
+ # Synsema quick reference (v0.6.10)
7
+
8
+ > Curated 10 KB summary for the agent (the full reference is ~450 KB and lives in the user's editor
9
+ > skill). Kept in sync by hand with each `synsema update`; if `synsema --version` is newer than the
10
+ > version above, trust the runtime's error messages over this file.
11
+
12
+ ## Dev loop
13
+ - `synsema check file.syn` (parse + validates every `use` import) · `synsema run file.syn` ·
14
+ `synsema test file.syn` (runs `test "..."` blocks) · `synsema serve file.syn` (HTTP server) ·
15
+ `synsema update` (self-update; then refresh the AI skill with the command it prints).
16
+ - Errors carry `file:line` and a suggestion. Read them; they are usually right.
17
+
18
+ ## Syntax reflexes (Python → Synsema)
19
+ - `let x be 5` / `set x to 6` (no `=`) · `-- comment` · `when / otherwise when / otherwise` (no colons)
20
+ - `each x in xs` (lists only; maps → `each k in keys(m)`) · `while c` · `task f(x)` … `give v`
21
+ - `nothing / true / false` · backtick strings interpolate: `` `n={n}` `` (double-quoted do NOT)
22
+ - `try` … `recover err` (recover SWALLOWS; `raise(err)` to re-throw) · `raise("msg")`
23
+ - `contains(xs, x)` (on maps checks KEYS) · `append(xs, x)` returns a NEW list → `set xs to append(xs, x)`
24
+ - `apply(f, xs)`, `where(xs, p)`, `sort_by(xs, f)`, `slice(xs, a, b)`, `split/join/trim/lower/upper`
25
+ - `json_encode / json_decode` · `length` · `text(x)` · `number(s)` (ALWAYS float → `floor()` for ints)
26
+ - Modules: `use "./m.syn" as m` (local only, never `../`), `export task/let`. A module cannot have
27
+ top-level `require` or `serve` — the ENTRY file grants capabilities.
28
+
29
+ ## Capabilities (deny-by-default)
30
+ - Declare at the top: `require net("host")`, `require file("dir")` + `require file("dir/*")`,
31
+ `require exec`, `require env("X_*")`, `require secret("KEY")`, `require serve(8080)`, `require time`.
32
+ - A task's own top-of-body `require` lines are what `call_tool(task, args_map)` intersects
33
+ (least-privilege). Plain calls run with the program's ambient capabilities.
34
+ - `sandbox` blocks strip everything. `secret("K")` is opaque: pass it as a header, never print it.
35
+
36
+ ## Processes
37
+ - `run(cmd, [args], timeout?, {cwd, env, stdin})` → `{exit_code, stdout, stderr}`; captures everything,
38
+ returns at the end; non-zero exit is DATA (only timeout / can't-launch raise).
39
+ - Live process (v0.6.7+): `let p be proc_spawn(cmd, [args], {cwd})` then `proc_recv(p, secs)` →
40
+ `{type: "stdout"|"stderr"|"exit", data}` or `nothing`; `proc_send(p, text)`, `proc_kill(p)`, `proc_close(p)`.
41
+ `select({"a": handle, "b": handle}, secs)` waits on processes, sockets and bus at once (`ev["name"]`).
42
+ - **Pseudo-terminal (v0.6.8+)**: `proc_spawn(cmd, args, {"pty": true, "cols": 120, "rows": 40})` for
43
+ y/N prompts, passwords, REPLs, TUIs. One `stdout` stream of TEXT chunks with ANSI (never split inside a UTF-8 char);
44
+ `strip_ansi(text)` shows what a human sees; keys go with `proc_send` (Enter = `"\r"`, Ctrl-C = `bytes([3])`);
45
+ `proc_resize(p, cols, rows)`. Web terminal = `socket` route + pty in one `select` (xterm.js renders).
46
+ - **Tree kill (v0.6.9+)**: `proc_kill`/`proc_close` reach the WHOLE process tree (Job Object on Windows,
47
+ process group on unix) — `sh -c "npm run dev"` takes its `node` with it; `proc_stats(p)["tree"]` confirms.
48
+ `{"process_group": false}` deliberately detaches a daemon. A grandchild holding the pipe cannot hang you (1 s grace).
49
+ - Every live process dies with its interpreter — **under `serve` that means the END OF THE REQUEST**: a
50
+ `proc_spawn` in a handler is gone when the handler returns. A process that must outlive requests lives
51
+ inside an `agent` spawned from the handler (own lifecycle; blackboard `share/observe` + `bus_*` are shared
52
+ with handlers). That is how lampson's `process` tool works (`lib/tools/proc.syn`).
53
+ - **File watch (v0.6.9+)**: `let w be watch("src", {"interval": 0.2, "ignore": ["*.tmp"]})` → events
54
+ `{type: "create"|"modify"|"delete", path, is_dir}` via `watch_recv(w, secs)` or `select`; polling with a
55
+ snapshot (latency = interval), `watch_close(w)`. Gate: `file("src")` + `file("src/*")`.
56
+
57
+ ## Traps verified on this machine (do not fight them)
58
+ - Agents: an `agent` body sees ONLY its `spawn X with a = …` parameters, the builtins and the TOP-LEVEL
59
+ tasks of the entry program — not the module's `let` constants, tasks nor `c.*` imports ("Undefined
60
+ variable"); a task passed through `spawn` arrives as text. Each agent needs its OWN `require` lines.
61
+ `stop` only inside loops. `synsema run`/`test` join agents at the end: a `while true` supervisor must be
62
+ told to stop (bus/signal). (≤ 0.6.9 `test` had no swarm; v0.6.10+ runs agents for real in `test`.)
63
+ - `;` between statements is a lexer error (one statement per line).
64
+ - `file("./*")` behaves like `"*"` (whole disk). Use a named dir scope: `file("workspace/*")`.
65
+ - `http_post(url, MAP)` sends `text(map)`, not JSON → `http_post(url, json_encode(body), {"Content-Type": "application/json"})`.
66
+ - Responses have `status, ok, body, headers` — no `json` key → `json_decode(body of r)`; on network error: `status 0` + `error`.
67
+ - `localhost` resolves to IPv6; use `127.0.0.1`.
68
+ - `run("bash", ...)` hangs on Windows (WSL bash) → `C:\Program Files\Git\bin\bash.exe` or `cmd /c`.
69
+ - A task named `run` shadows the builtin `run` (infinite recursion).
70
+ - Reserved words that break variable/param names: `reason task ask stop decide analyze generate show approve confirm`.
71
+ - `and`/`or` do NOT short-circuit → nest `when` before indexing.
72
+ - No `merge`: add a key with `set m["k"] to v`. No `append_file`: read + write (atomic; parents created).
73
+ - Runtime error messages are Capitalized (`Not a directory: …`) and `contains` is case-sensitive → compare `lower(text(err))`.
74
+ - Under `serve`: resolve `secret()` inside the handler; define tasks before `serve on`; `send` only inside `stream`.
75
+ - `.env` values override shell-exported ones if set there.