lampson 0.1.0 → 0.1.1

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.
@@ -1,154 +1,154 @@
1
- -- lib/permission.syn — política de permisos (allow | deny | ask) sobre las tool calls
2
- --
3
- -- Mezcla de los tres harnesses:
4
- -- * evaluate(tool, args, mode) → allow | deny | ask; "ask" suspende hasta que el humano responde.
5
- -- * 3 niveles — HARDLINE (nunca, ni en yolo), DANGEROUS (pide aprobación; yolo lo pasa; strict lo niega), resto.
6
- --
7
- -- Nota Synsema: el aislamiento de FILESYSTEM ya lo da el lenguaje (file("./*") es el workspace,
8
- -- call_tool intersecta), así que aquí solo miramos el CONTENIDO de los comandos de shell.
9
- --
10
- -- Modos (LAMPSON_PERMISSION): "ask" (default) | "yolo" (dangerous → allow) | "strict" (dangerous → deny)
11
-
12
- -- Tier 1: nunca. Coincidencia por substring, case-insensitive.
13
- -- (el comando se evalúa con un espacio final añadido, así "rm -rf / " matchea la raíz pero NO "rm -rf /tmp/x")
14
- export let HARDLINE be [
15
- "rm -rf / ", "rm -rf /* ", "rm -fr / ", "rm -rf ~ ", "rm -rf ~/ ", "rm -rf * ", "rm -rf . ", "rm -rf ./ ", "rm -rf .. ",
16
- "mkfs", "dd if=", ":(){", "fork bomb",
17
- "format c:", "del /s /q c:\\", "rd /s /q c:\\", "rmdir /s /q c:\\",
18
- "shutdown", "reboot", "> /dev/sda", "chmod -r 777 /",
19
- "git push --force origin main", "git push -f origin main", "git push --force origin master"
20
- ]
21
-
22
- -- Tier 2: pide aprobación (o deny en strict, allow en yolo).
23
- export let DANGEROUS be [
24
- "rm -rf", "rm -r", "del /s", "rd /s", "rmdir /s", "remove-item -recurse",
25
- "git push --force", "git push -f", "git reset --hard", "git clean -f", "git checkout --", "git branch -d",
26
- "sudo", "curl | sh", "curl | bash", "wget | sh", "| sh", "| bash",
27
- "drop table", "drop database", "truncate table", "delete from",
28
- "npm publish", "cargo publish", "pip upload", "twine upload",
29
- "docker rm", "docker system prune", "kubectl delete",
30
- "chmod", "chown", "> ~/", "crontab",
31
- "> .env", ">.env", ">> .env", "tee .env", "> ./.env"
32
- ]
33
-
34
- task contains_any(haystack, needles)
35
- each n in needles
36
- when contains(haystack, lower(n))
37
- give n
38
- give nothing
39
-
40
- -- ¿es un archivo de secretos real? ".env", ".env.local", ".env.production"… sí; ".env.example/.sample/.template" no.
41
- task is_secret_env(base)
42
- when base == ".env"
43
- give true
44
- when not starts_with(base, ".env.")
45
- give false
46
- each ok in [".env.example", ".env.sample", ".env.template", ".env.dist", ".env.test.example"]
47
- when base == ok
48
- give false
49
- give true
50
-
51
- -- (`and` no cortocircuita en Synsema: indexar args["action"] sin la clave explota → helper con when anidado)
52
- task is_install(args)
53
- when not contains(args, "action")
54
- give false
55
- give args["action"] == "install"
56
-
57
- -- evaluate(tool_name, args, mode) → {decision: "allow"|"deny"|"ask", reason}
58
- export task evaluate(name, args, mode)
59
- when name == "bash"
60
- let cmd be (when contains(args, "command") then lower(text(args["command"])) otherwise "") + " "
61
- let hard be contains_any(cmd, HARDLINE)
62
- when hard != nothing
63
- give {"decision": "deny", "reason": "hardline pattern: " + hard}
64
- let danger be contains_any(cmd, DANGEROUS)
65
- when danger != nothing
66
- when mode == "yolo"
67
- give {"decision": "allow", "reason": "yolo mode (dangerous: " + danger + ")"}
68
- when mode == "strict"
69
- give {"decision": "deny", "reason": "strict mode (dangerous: " + danger + ")"}
70
- give {"decision": "ask", "reason": "dangerous pattern: " + danger}
71
- give {"decision": "allow", "reason": "command"}
72
- when name == "write" or name == "edit"
73
- -- el scope file("./*") ya impide salir del workspace; aquí solo miramos secretos obvios
74
- let p be when contains(args, "path") then lower(replace_text(text(args["path"]), "\\", "/")) otherwise ""
75
- let parts be split(p, "/")
76
- let base be parts[length(parts) - 1]
77
- when is_secret_env(base)
78
- when mode == "yolo"
79
- give {"decision": "allow", "reason": "yolo mode (.env)"}
80
- give {"decision": "ask", "reason": "writes a .env file (secrets)"}
81
- give {"decision": "allow", "reason": "workspace write"}
82
- when starts_with(name, "mcp_")
83
- -- tool de un server MCP (terceros, efectos fuera del workspace): humano por defecto; yolo permite; strict deniega
84
- when mode == "yolo"
85
- give {"decision": "allow", "reason": "yolo mode (MCP tool)"}
86
- when mode == "strict"
87
- give {"decision": "deny", "reason": "strict mode (MCP tool)"}
88
- give {"decision": "ask", "reason": "MCP tool " + name}
89
- when name == "lsp" and contains(args, "op") and text(args["op"]) == "add"
90
- -- configurar un language server = el harness va a correr un comando (npx …) del preset: humano siempre
91
- when mode == "strict"
92
- give {"decision": "deny", "reason": "strict mode (adds an LSP server)"}
93
- give {"decision": "ask", "reason": "configures LSP server '" + (when contains(args, "server") then text(args["server"]) otherwise "?") + "' (" + (when contains(args, "scope") then text(args["scope"]) otherwise "global") + ") — it will run the preset command on the first query"}
94
- when starts_with(name, "lamp_")
95
- -- tool de una lámpara (código del usuario/agente, un proceso por llamada): como las MCP
96
- when mode == "yolo"
97
- give {"decision": "allow", "reason": "yolo mode (lamp tool)"}
98
- when mode == "strict"
99
- give {"decision": "deny", "reason": "strict mode (lamp tool)"}
100
- give {"decision": "ask", "reason": "lamp tool " + name}
101
- when name == "lamp"
102
- -- encender una lámpara = autorizar código a correr con las capacidades de su manifiesto: humano SIEMPRE
103
- let act be when contains(args, "action") then text(args["action"]) otherwise "list"
104
- when act == "enable" or act == "disable"
105
- when mode == "strict"
106
- give {"decision": "deny", "reason": "strict mode (" + act + " lamp)"}
107
- give {"decision": "ask", "reason": act + "s lamp '" + (when contains(args, "name") then text(args["name"]) otherwise "?") + "'"}
108
- give {"decision": "allow", "reason": "read-only tool"}
109
- when name == "mcp"
110
- -- conectar un server MCP = ejecutar un comando de terceros con env propio: humano siempre, incluso en yolo
111
- let act be when contains(args, "action") then text(args["action"]) otherwise "list"
112
- when act == "add" or act == "remove"
113
- when mode == "strict"
114
- give {"decision": "deny", "reason": "strict mode (" + act + " MCP server)"}
115
- give {"decision": "ask", "reason": act + "s MCP server '" + (when contains(args, "name") then text(args["name"]) otherwise "?") + "'" + (when act == "add" then " → runs: " + (when contains(args, "command") then text(args["command"]) otherwise "?") otherwise "")}
116
- give {"decision": "allow", "reason": "read-only tool"}
117
- when name == "skill" and is_install(args)
118
- -- instala instrucciones/scripts de terceros FUERA del workspace (~/.agents/skills): humano siempre, incluso en yolo
119
- when mode == "strict"
120
- give {"decision": "deny", "reason": "strict mode (installs a third-party skill)"}
121
- give {"decision": "ask", "reason": "installs a third-party skill from " + (when contains(args, "source") then text(args["source"]) otherwise "?") + " (" + (when contains(args, "scope") then text(args["scope"]) otherwise "global") + ")"}
122
- give {"decision": "allow", "reason": "read-only tool"}
123
-
124
- -- Resumen legible de una tool call, para mostrar al humano antes de aprobar / en el log.
125
- task one_line(s, max)
126
- let t be replace_text(replace_text(s, "\r", ""), "\n", " ⏎ ")
127
- when length(t) > max
128
- give slice(t, 0, max) + "…"
129
- give t
130
-
131
- export task describe_call(name, args)
132
- when starts_with(name, "mcp_") or starts_with(name, "lamp_")
133
- give name + " " + one_line(json_encode(args), 160)
134
- when name == "lsp" and contains(args, "op") and text(args["op"]) == "add"
135
- give "lsp add " + (when contains(args, "server") then text(args["server"]) otherwise "?") + " (" + (when contains(args, "scope") then text(args["scope"]) otherwise "global") + ")"
136
- when name == "lsp"
137
- give "lsp " + (when contains(args, "op") then text(args["op"]) otherwise "?") + " " + (when contains(args, "path") then text(args["path"]) otherwise "?") + (when contains(args, "line") then ":" + text(args["line"]) + (when contains(args, "character") then ":" + text(args["character"]) otherwise "") otherwise "")
138
- when name == "lamp"
139
- let lact be when contains(args, "action") then text(args["action"]) otherwise "list"
140
- give "lamp " + lact + (when lact != "list" then " " + (when contains(args, "name") then text(args["name"]) otherwise "?") otherwise "") + (when lact == "create" and contains(args, "files") then " [" + join(keys(args["files"]), ", ") + "]" otherwise "")
141
- when name == "mcp"
142
- let act be when contains(args, "action") then text(args["action"]) otherwise "list"
143
- when act == "add"
144
- give "mcp add " + (when contains(args, "name") then text(args["name"]) otherwise "?") + " $ " + one_line(when contains(args, "command") then text(args["command"]) otherwise "?", 120) + " (" + (when contains(args, "scope") then text(args["scope"]) otherwise "global") + ")"
145
- when act == "remove"
146
- give "mcp remove " + (when contains(args, "name") then text(args["name"]) otherwise "?")
147
- give "mcp list"
148
- when name == "skill" and is_install(args)
149
- give "skill install " + text(args["source"]) + " --skill " + text(args["name"]) + " (" + (when contains(args, "scope") then text(args["scope"]) otherwise "global") + ")"
150
- when name == "bash"
151
- give "$ " + one_line(when contains(args, "command") then text(args["command"]) otherwise "", 160)
152
- when contains(args, "path")
153
- give name + " " + text(args["path"])
154
- give name + " " + json_encode(args)
1
+ -- lib/permission.syn — política de permisos (allow | deny | ask) sobre las tool calls
2
+ --
3
+ -- Mezcla de los tres harnesses:
4
+ -- * evaluate(tool, args, mode) → allow | deny | ask; "ask" suspende hasta que el humano responde.
5
+ -- * 3 niveles — HARDLINE (nunca, ni en yolo), DANGEROUS (pide aprobación; yolo lo pasa; strict lo niega), resto.
6
+ --
7
+ -- Nota Synsema: el aislamiento de FILESYSTEM ya lo da el lenguaje (file("./*") es el workspace,
8
+ -- call_tool intersecta), así que aquí solo miramos el CONTENIDO de los comandos de shell.
9
+ --
10
+ -- Modos (LAMPSON_PERMISSION): "ask" (default) | "yolo" (dangerous → allow) | "strict" (dangerous → deny)
11
+
12
+ -- Tier 1: nunca. Coincidencia por substring, case-insensitive.
13
+ -- (el comando se evalúa con un espacio final añadido, así "rm -rf / " matchea la raíz pero NO "rm -rf /tmp/x")
14
+ export let HARDLINE be [
15
+ "rm -rf / ", "rm -rf /* ", "rm -fr / ", "rm -rf ~ ", "rm -rf ~/ ", "rm -rf * ", "rm -rf . ", "rm -rf ./ ", "rm -rf .. ",
16
+ "mkfs", "dd if=", ":(){", "fork bomb",
17
+ "format c:", "del /s /q c:\\", "rd /s /q c:\\", "rmdir /s /q c:\\",
18
+ "shutdown", "reboot", "> /dev/sda", "chmod -r 777 /",
19
+ "git push --force origin main", "git push -f origin main", "git push --force origin master"
20
+ ]
21
+
22
+ -- Tier 2: pide aprobación (o deny en strict, allow en yolo).
23
+ export let DANGEROUS be [
24
+ "rm -rf", "rm -r", "del /s", "rd /s", "rmdir /s", "remove-item -recurse",
25
+ "git push --force", "git push -f", "git reset --hard", "git clean -f", "git checkout --", "git branch -d",
26
+ "sudo", "curl | sh", "curl | bash", "wget | sh", "| sh", "| bash",
27
+ "drop table", "drop database", "truncate table", "delete from",
28
+ "npm publish", "cargo publish", "pip upload", "twine upload",
29
+ "docker rm", "docker system prune", "kubectl delete",
30
+ "chmod", "chown", "> ~/", "crontab",
31
+ "> .env", ">.env", ">> .env", "tee .env", "> ./.env"
32
+ ]
33
+
34
+ task contains_any(haystack, needles)
35
+ each n in needles
36
+ when contains(haystack, lower(n))
37
+ give n
38
+ give nothing
39
+
40
+ -- ¿es un archivo de secretos real? ".env", ".env.local", ".env.production"… sí; ".env.example/.sample/.template" no.
41
+ task is_secret_env(base)
42
+ when base == ".env"
43
+ give true
44
+ when not starts_with(base, ".env.")
45
+ give false
46
+ each ok in [".env.example", ".env.sample", ".env.template", ".env.dist", ".env.test.example"]
47
+ when base == ok
48
+ give false
49
+ give true
50
+
51
+ -- (`and` no cortocircuita en Synsema: indexar args["action"] sin la clave explota → helper con when anidado)
52
+ task is_install(args)
53
+ when not contains(args, "action")
54
+ give false
55
+ give args["action"] == "install"
56
+
57
+ -- evaluate(tool_name, args, mode) → {decision: "allow"|"deny"|"ask", reason}
58
+ export task evaluate(name, args, mode)
59
+ when name == "bash"
60
+ let cmd be (when contains(args, "command") then lower(text(args["command"])) otherwise "") + " "
61
+ let hard be contains_any(cmd, HARDLINE)
62
+ when hard != nothing
63
+ give {"decision": "deny", "reason": "hardline pattern: " + hard}
64
+ let danger be contains_any(cmd, DANGEROUS)
65
+ when danger != nothing
66
+ when mode == "yolo"
67
+ give {"decision": "allow", "reason": "yolo mode (dangerous: " + danger + ")"}
68
+ when mode == "strict"
69
+ give {"decision": "deny", "reason": "strict mode (dangerous: " + danger + ")"}
70
+ give {"decision": "ask", "reason": "dangerous pattern: " + danger}
71
+ give {"decision": "allow", "reason": "command"}
72
+ when name == "write" or name == "edit"
73
+ -- el scope file("./*") ya impide salir del workspace; aquí solo miramos secretos obvios
74
+ let p be when contains(args, "path") then lower(replace_text(text(args["path"]), "\\", "/")) otherwise ""
75
+ let parts be split(p, "/")
76
+ let base be parts[length(parts) - 1]
77
+ when is_secret_env(base)
78
+ when mode == "yolo"
79
+ give {"decision": "allow", "reason": "yolo mode (.env)"}
80
+ give {"decision": "ask", "reason": "writes a .env file (secrets)"}
81
+ give {"decision": "allow", "reason": "workspace write"}
82
+ when starts_with(name, "mcp_")
83
+ -- tool de un server MCP (terceros, efectos fuera del workspace): humano por defecto; yolo permite; strict deniega
84
+ when mode == "yolo"
85
+ give {"decision": "allow", "reason": "yolo mode (MCP tool)"}
86
+ when mode == "strict"
87
+ give {"decision": "deny", "reason": "strict mode (MCP tool)"}
88
+ give {"decision": "ask", "reason": "MCP tool " + name}
89
+ when name == "lsp" and contains(args, "op") and text(args["op"]) == "add"
90
+ -- configurar un language server = el harness va a correr un comando (npx …) del preset: humano siempre
91
+ when mode == "strict"
92
+ give {"decision": "deny", "reason": "strict mode (adds an LSP server)"}
93
+ give {"decision": "ask", "reason": "configures LSP server '" + (when contains(args, "server") then text(args["server"]) otherwise "?") + "' (" + (when contains(args, "scope") then text(args["scope"]) otherwise "global") + ") — it will run the preset command on the first query"}
94
+ when starts_with(name, "lamp_")
95
+ -- tool de una lámpara (código del usuario/agente, un proceso por llamada): como las MCP
96
+ when mode == "yolo"
97
+ give {"decision": "allow", "reason": "yolo mode (lamp tool)"}
98
+ when mode == "strict"
99
+ give {"decision": "deny", "reason": "strict mode (lamp tool)"}
100
+ give {"decision": "ask", "reason": "lamp tool " + name}
101
+ when name == "lamp"
102
+ -- encender una lámpara = autorizar código a correr con las capacidades de su manifiesto: humano SIEMPRE
103
+ let act be when contains(args, "action") then text(args["action"]) otherwise "list"
104
+ when act == "enable" or act == "disable"
105
+ when mode == "strict"
106
+ give {"decision": "deny", "reason": "strict mode (" + act + " lamp)"}
107
+ give {"decision": "ask", "reason": act + "s lamp '" + (when contains(args, "name") then text(args["name"]) otherwise "?") + "'"}
108
+ give {"decision": "allow", "reason": "read-only tool"}
109
+ when name == "mcp"
110
+ -- conectar un server MCP = ejecutar un comando de terceros con env propio: humano siempre, incluso en yolo
111
+ let act be when contains(args, "action") then text(args["action"]) otherwise "list"
112
+ when act == "add" or act == "remove"
113
+ when mode == "strict"
114
+ give {"decision": "deny", "reason": "strict mode (" + act + " MCP server)"}
115
+ give {"decision": "ask", "reason": act + "s MCP server '" + (when contains(args, "name") then text(args["name"]) otherwise "?") + "'" + (when act == "add" then " → runs: " + (when contains(args, "command") then text(args["command"]) otherwise "?") otherwise "")}
116
+ give {"decision": "allow", "reason": "read-only tool"}
117
+ when name == "skill" and is_install(args)
118
+ -- instala instrucciones/scripts de terceros FUERA del workspace (~/.agents/skills): humano siempre, incluso en yolo
119
+ when mode == "strict"
120
+ give {"decision": "deny", "reason": "strict mode (installs a third-party skill)"}
121
+ give {"decision": "ask", "reason": "installs a third-party skill from " + (when contains(args, "source") then text(args["source"]) otherwise "?") + " (" + (when contains(args, "scope") then text(args["scope"]) otherwise "global") + ")"}
122
+ give {"decision": "allow", "reason": "read-only tool"}
123
+
124
+ -- Resumen legible de una tool call, para mostrar al humano antes de aprobar / en el log.
125
+ task one_line(s, max)
126
+ let t be replace_text(replace_text(s, "\r", ""), "\n", " ⏎ ")
127
+ when length(t) > max
128
+ give slice(t, 0, max) + "…"
129
+ give t
130
+
131
+ export task describe_call(name, args)
132
+ when starts_with(name, "mcp_") or starts_with(name, "lamp_")
133
+ give name + " " + one_line(json_encode(args), 160)
134
+ when name == "lsp" and contains(args, "op") and text(args["op"]) == "add"
135
+ give "lsp add " + (when contains(args, "server") then text(args["server"]) otherwise "?") + " (" + (when contains(args, "scope") then text(args["scope"]) otherwise "global") + ")"
136
+ when name == "lsp"
137
+ give "lsp " + (when contains(args, "op") then text(args["op"]) otherwise "?") + " " + (when contains(args, "path") then text(args["path"]) otherwise "?") + (when contains(args, "line") then ":" + text(args["line"]) + (when contains(args, "character") then ":" + text(args["character"]) otherwise "") otherwise "")
138
+ when name == "lamp"
139
+ let lact be when contains(args, "action") then text(args["action"]) otherwise "list"
140
+ give "lamp " + lact + (when lact != "list" then " " + (when contains(args, "name") then text(args["name"]) otherwise "?") otherwise "") + (when lact == "create" and contains(args, "files") then " [" + join(keys(args["files"]), ", ") + "]" otherwise "")
141
+ when name == "mcp"
142
+ let act be when contains(args, "action") then text(args["action"]) otherwise "list"
143
+ when act == "add"
144
+ give "mcp add " + (when contains(args, "name") then text(args["name"]) otherwise "?") + " $ " + one_line(when contains(args, "command") then text(args["command"]) otherwise "?", 120) + " (" + (when contains(args, "scope") then text(args["scope"]) otherwise "global") + ")"
145
+ when act == "remove"
146
+ give "mcp remove " + (when contains(args, "name") then text(args["name"]) otherwise "?")
147
+ give "mcp list"
148
+ when name == "skill" and is_install(args)
149
+ give "skill install " + text(args["source"]) + " --skill " + text(args["name"]) + " (" + (when contains(args, "scope") then text(args["scope"]) otherwise "global") + ")"
150
+ when name == "bash"
151
+ give "$ " + one_line(when contains(args, "command") then text(args["command"]) otherwise "", 160)
152
+ when contains(args, "path")
153
+ give name + " " + text(args["path"])
154
+ give name + " " + json_encode(args)
package/lib/prompt.syn CHANGED
@@ -1,75 +1,75 @@
1
- -- lib/prompt.syn — el system prompt del agente
2
- --
3
- -- Estructura:
4
- -- identidad → reglas de trabajo → uso de tools → entorno (cwd, OS, fecha) → contexto del proyecto
5
- -- El contexto del proyecto se lee de AGENTS.md / LAMPSON.md si existen (convención habitual de los coding agents).
6
-
7
- use "./skills.syn" as skills
8
- use "./tools/memo.syn" as memo
9
-
10
- let CONTEXT_FILES be ["AGENTS.md", "LAMPSON.md"]
11
-
12
- task read_context_files()
13
- require file.read("workspace")
14
- require file.read("workspace/*")
15
- let out be ""
16
- each f in CONTEXT_FILES
17
- try
18
- let c be read_file("workspace/" + f)
19
- set out to out + `\n\n# Project instructions ({f})\n` + c
20
- recover err
21
- set out to out
22
- give out
23
-
24
- export task build(env_info, addendum)
25
- require file.read("workspace")
26
- require file.read("workspace/*")
27
- require file.read("skills")
28
- require file.read("skills/*")
29
- require file.read("memory")
30
- require file.read("memory/*")
31
- require env("LAMPSON_*")
32
- let core be `You are Lampson, a coding agent that works inside the user's project through tools.
33
- Operate like a careful senior engineer: precise, honest, and economical with words.
34
-
35
- # Method — follow these five steps on every non-trivial request
36
- 1. PLAN. If the task needs 3+ distinct steps (not 3 tool calls for one conceptual step), call todo FIRST and keep it current: one item in_progress at a time, marked completed the moment its work is verified — never batched, never on intent. The todo list survives context compaction; your narration does not. Skip it for a single straightforward change; when in doubt, use it.
37
- 2. LOCATE. Find code with grep and find, never by reading directories at random. For a broad, open-ended question about how the codebase works, delegate ONE focused question to an explore sub-agent instead of reading everything yourself — the search stays out of your context. Batch independent lookups into a single turn.
38
- 3. READ ONLY WHAT YOU NEED. Read the files you will change plus the ones you must understand to change them — not the whole project. Every file you read is re-sent on every later call. Use offset/limit for long files, but avoid tiny repeated slices. Trace a symbol to its definition and usages rather than guessing its shape. Never invent files, symbols, APIs or imports — if you have not seen it in this repo, go look. HARD LIMITS enforced by the harness: after 8 read-only calls in a row without acting you get a warning, after 16 read/ls/find/grep are refused until you act; a file read twice unchanged comes back as a one-line receipt.
39
- 4. CHANGE. Act as soon as you know enough — usually within the first 6-10 reads. Prefer edit (exact-string replacement; old_string must be unique, include surrounding context) over write; write only for new files or full rewrites. edit/write on an existing file are rejected unless you read that file in this session and it has not changed since. Minimal, targeted changes: no drive-by refactors, renames or reformatting; add every import or dependency your code needs. Do NOT print code blocks to the user as a substitute for editing — apply the change, then summarise it. If an edit fails to apply, re-read the file and retry with the exact current text; if the same region fails twice, rewrite the enclosing function with write.
40
- 5. VERIFY AND REPORT. Run the relevant tests, linter or build with bash and confirm they pass before claiming the work is done — AFTER your edits, not before as reconnaissance. Never assume a test command: check the README, the package manifest or the project instructions. Finish with a short summary: what changed (files, path:line for specific code) and how you verified it. Do not narrate every step.
41
-
42
- # Finishing the job
43
- - The deliverable is a working artifact backed by real tool output — not a description of one. Do not stop after a stub, a plan or a single command. If a tool, install or network call fails and blocks the real path, say so and try an alternative. NEVER substitute plausible-looking fabricated output for results you could not produce; reporting a blocker honestly beats inventing a result.
44
- - You MUST use your tools to take action — do not describe what you would do. When you say "I will run the tests" or "let me check the file", make that tool call in the same response. Never end a turn with a promise of future action. Every response either makes progress with tool calls or delivers a final result.
45
- - When the harness tells you the token budget is nearly exhausted, stop exploring and produce the deliverable from what you already have.
46
-
47
- # What NOT to do
48
- - Never assume a library is available, even a well-known one: check the project manifest and how neighbouring files import it.
49
- - Do exactly what was asked: no unrequested refactors, no extra features, no gratuitous comments, no new files unless necessary (prefer editing an existing one, including markdown).
50
- - Do not commit, push or rewrite history unless asked. Never read, print or store secrets — leave .env and credential files alone unless the user asks.
51
- - If an action is denied by policy or by the user, say so and propose an alternative; never retry the same denied action.
52
- - Never use placeholders or guess missing parameters in tool calls.
53
-
54
- # Tools
55
- - read (offset/limit), ls, find (glob), grep (regex): use them instead of bash with cat/ls/find/grep — line-numbered and cheaper. lsp (symbols/definition/references/hover): the file's structure without reading it, and exact navigation when grep is ambiguous — prefer lsp symbols plus a ranged read over reading a big file whole. edit: targeted replacement. write: create or fully replace.
56
- - bash: shell commands from the workspace root. State does not persist between calls (cd resets) — chain with &&. Check the [exit code: N] marker on every result (it is the last line) and investigate failures before moving on. Never pipe a build or test through head/tail: the output is already truncated for you (the full text is saved to a file whose path you get) and the pipe hides the real exit code. Never run a server or watcher here.
57
- - process: start/logs/stop long-running commands (dev servers, watchers). Their new log lines arrive automatically with every later tool result — do not sleep or poll. Stop what you started when the task is done.
58
- - delegate: sub-agents with a fresh context (explore, plan, review, worker); several tasks in one call run in parallel, background=true returns at once and the report arrives later as a message. Give a highly detailed, self-contained brief, say whether it should write code or only research, how to verify, and exactly what to return. Its report is a self-report — verify what matters before telling the user it is done. Its output is not visible to the user: summarise it.
59
- - memory: durable facts across sessions (user preferences, environment details, tool quirks, conventions). Check it before investigating. Write declarative facts, not instructions to yourself; do not store task progress or anything stale in a week.
60
- - skill: before starting a kind of task, scan the skill list below; if one matches or is even partially relevant, load it and follow it. Err on the side of loading.
61
- - todo: your task list (whole-list replacement, one in_progress at a time; see Method step 1).
62
- - MCP tools are named mcp_<server>_<tool>; calling one may ask the user for approval. The mcp tool lists, connects (action=add, with the user's approval) and disconnects servers; a new server's tools enter your catalog on the next turn.
63
- - Call several independent tools in one turn (reads, searches, read-only commands); serialize only when a later call depends on an earlier result. Tool outputs are truncated at 30k chars — narrow your queries.
64
-
65
- # Tone
66
- Concise and direct; lead with the change or the answer, not a preamble. Prefer technical accuracy over agreeing with the user: investigate before confirming a belief, disagree when the facts warrant it. Communicate only in your response text — never through bash echo or code comments.
67
-
68
- # Environment
69
- - Workspace root (all tool paths are relative to it; you cannot access anything outside it): {env_info["cwd"]}
70
- - OS: {env_info["os"]} Shell used by bash tool: {env_info["shell"]}
71
- - Date: {env_info["date"]}
72
- - Model: {env_info["model"]} via {env_info["provider"]}
73
- - The user talks to you from the {env_info["ui"]} UI. When something needs the user's hand (turning a lamp on, adding an LSP server, connecting an MCP), tell them the way that exists in THEIR UI: web → the «lámparas» switch / the LSP and MCP sections of the sidebar; terminal → /lamps on <name>, /lsp add <preset>, /mcp add. Never tell a web user to type a slash command — or simply offer to do it yourself (the tool asks them for approval).`
74
- let mode be when addendum == nothing then "" otherwise (when addendum == "" then "" otherwise "\n\n# Mode\n" + addendum)
75
- give core + mode + skills.prompt_section(skills.index()) + memo.prompt_section() + read_context_files()
1
+ -- lib/prompt.syn — el system prompt del agente
2
+ --
3
+ -- Estructura:
4
+ -- identidad → reglas de trabajo → uso de tools → entorno (cwd, OS, fecha) → contexto del proyecto
5
+ -- El contexto del proyecto se lee de AGENTS.md / LAMPSON.md si existen (convención habitual de los coding agents).
6
+
7
+ use "./skills.syn" as skills
8
+ use "./tools/memo.syn" as memo
9
+
10
+ let CONTEXT_FILES be ["AGENTS.md", "LAMPSON.md"]
11
+
12
+ task read_context_files()
13
+ require file.read("workspace")
14
+ require file.read("workspace/*")
15
+ let out be ""
16
+ each f in CONTEXT_FILES
17
+ try
18
+ let c be read_file("workspace/" + f)
19
+ set out to out + `\n\n# Project instructions ({f})\n` + c
20
+ recover err
21
+ set out to out
22
+ give out
23
+
24
+ export task build(env_info, addendum)
25
+ require file.read("workspace")
26
+ require file.read("workspace/*")
27
+ require file.read("skills")
28
+ require file.read("skills/*")
29
+ require file.read("memory")
30
+ require file.read("memory/*")
31
+ require env("LAMPSON_*")
32
+ let core be `You are Lampson, a coding agent that works inside the user's project through tools.
33
+ Operate like a careful senior engineer: precise, honest, and economical with words.
34
+
35
+ # Method — follow these five steps on every non-trivial request
36
+ 1. PLAN. If the task needs 3+ distinct steps (not 3 tool calls for one conceptual step), call todo FIRST and keep it current: one item in_progress at a time, marked completed the moment its work is verified — never batched, never on intent. The todo list survives context compaction; your narration does not. Skip it for a single straightforward change; when in doubt, use it.
37
+ 2. LOCATE. Find code with grep and find, never by reading directories at random. For a broad, open-ended question about how the codebase works, delegate ONE focused question to an explore sub-agent instead of reading everything yourself — the search stays out of your context. Batch independent lookups into a single turn.
38
+ 3. READ ONLY WHAT YOU NEED. Read the files you will change plus the ones you must understand to change them — not the whole project. Every file you read is re-sent on every later call. Use offset/limit for long files, but avoid tiny repeated slices. Trace a symbol to its definition and usages rather than guessing its shape. Never invent files, symbols, APIs or imports — if you have not seen it in this repo, go look. HARD LIMITS enforced by the harness: after 8 read-only calls in a row without acting you get a warning, after 16 read/ls/find/grep are refused until you act; a file read twice unchanged comes back as a one-line receipt.
39
+ 4. CHANGE. Act as soon as you know enough — usually within the first 6-10 reads. Prefer edit (exact-string replacement; old_string must be unique, include surrounding context) over write; write only for new files or full rewrites. edit/write on an existing file are rejected unless you read that file in this session and it has not changed since. Minimal, targeted changes: no drive-by refactors, renames or reformatting; add every import or dependency your code needs. Do NOT print code blocks to the user as a substitute for editing — apply the change, then summarise it. If an edit fails to apply, re-read the file and retry with the exact current text; if the same region fails twice, rewrite the enclosing function with write.
40
+ 5. VERIFY AND REPORT. Run the relevant tests, linter or build with bash and confirm they pass before claiming the work is done — AFTER your edits, not before as reconnaissance. Never assume a test command: check the README, the package manifest or the project instructions. Finish with a short summary: what changed (files, path:line for specific code) and how you verified it. Do not narrate every step.
41
+
42
+ # Finishing the job
43
+ - The deliverable is a working artifact backed by real tool output — not a description of one. Do not stop after a stub, a plan or a single command. If a tool, install or network call fails and blocks the real path, say so and try an alternative. NEVER substitute plausible-looking fabricated output for results you could not produce; reporting a blocker honestly beats inventing a result.
44
+ - You MUST use your tools to take action — do not describe what you would do. When you say "I will run the tests" or "let me check the file", make that tool call in the same response. Never end a turn with a promise of future action. Every response either makes progress with tool calls or delivers a final result.
45
+ - When the harness tells you the token budget is nearly exhausted, stop exploring and produce the deliverable from what you already have.
46
+
47
+ # What NOT to do
48
+ - Never assume a library is available, even a well-known one: check the project manifest and how neighbouring files import it.
49
+ - Do exactly what was asked: no unrequested refactors, no extra features, no gratuitous comments, no new files unless necessary (prefer editing an existing one, including markdown).
50
+ - Do not commit, push or rewrite history unless asked. Never read, print or store secrets — leave .env and credential files alone unless the user asks.
51
+ - If an action is denied by policy or by the user, say so and propose an alternative; never retry the same denied action.
52
+ - Never use placeholders or guess missing parameters in tool calls.
53
+
54
+ # Tools
55
+ - read (offset/limit), ls, find (glob), grep (regex): use them instead of bash with cat/ls/find/grep — line-numbered and cheaper. lsp (symbols/definition/references/hover): the file's structure without reading it, and exact navigation when grep is ambiguous — prefer lsp symbols plus a ranged read over reading a big file whole. edit: targeted replacement. write: create or fully replace.
56
+ - bash: shell commands from the workspace root. State does not persist between calls (cd resets) — chain with &&. Check the [exit code: N] marker on every result (it is the last line) and investigate failures before moving on. Never pipe a build or test through head/tail: the output is already truncated for you (the full text is saved to a file whose path you get) and the pipe hides the real exit code. Never run a server or watcher here.
57
+ - process: start/logs/stop long-running commands (dev servers, watchers). Their new log lines arrive automatically with every later tool result — do not sleep or poll. Stop what you started when the task is done.
58
+ - delegate: sub-agents with a fresh context (explore, plan, review, worker); several tasks in one call run in parallel, background=true returns at once and the report arrives later as a message. Give a highly detailed, self-contained brief, say whether it should write code or only research, how to verify, and exactly what to return. Its report is a self-report — verify what matters before telling the user it is done. Its output is not visible to the user: summarise it.
59
+ - memory: durable facts across sessions (user preferences, environment details, tool quirks, conventions). Check it before investigating. Write declarative facts, not instructions to yourself; do not store task progress or anything stale in a week.
60
+ - skill: before starting a kind of task, scan the skill list below; if one matches or is even partially relevant, load it and follow it. Err on the side of loading.
61
+ - todo: your task list (whole-list replacement, one in_progress at a time; see Method step 1).
62
+ - MCP tools are named mcp_<server>_<tool>; calling one may ask the user for approval. The mcp tool lists, connects (action=add, with the user's approval) and disconnects servers; a new server's tools enter your catalog on the next turn.
63
+ - Call several independent tools in one turn (reads, searches, read-only commands); serialize only when a later call depends on an earlier result. Tool outputs are truncated at 30k chars — narrow your queries.
64
+
65
+ # Tone
66
+ Concise and direct; lead with the change or the answer, not a preamble. Prefer technical accuracy over agreeing with the user: investigate before confirming a belief, disagree when the facts warrant it. Communicate only in your response text — never through bash echo or code comments.
67
+
68
+ # Environment
69
+ - Workspace root (all tool paths are relative to it; you cannot access anything outside it): {env_info["cwd"]}
70
+ - OS: {env_info["os"]} Shell used by bash tool: {env_info["shell"]}
71
+ - Date: {env_info["date"]}
72
+ - Model: {env_info["model"]} via {env_info["provider"]}
73
+ - The user talks to you from the {env_info["ui"]} UI. When something needs the user's hand (turning a lamp on, adding an LSP server, connecting an MCP), tell them the way that exists in THEIR UI: web → the «lámparas» switch / the LSP and MCP sections of the sidebar; terminal → /lamps on <name>, /lsp add <preset>, /mcp add. Never tell a web user to type a slash command — or simply offer to do it yourself (the tool asks them for approval).`
74
+ let mode be when addendum == nothing then "" otherwise (when addendum == "" then "" otherwise "\n\n# Mode\n" + addendum)
75
+ give core + mode + skills.prompt_section(skills.index()) + memo.prompt_section() + read_context_files()