Operación
CLI
Un solo binario autocontenido. Los comandos centrales:
synsema init [dir] # scaffold: hello.syn (tour del lenguaje + test), .env.example, .gitignore, .mcp.json (synsema-code)
synsema init [dir] --synfide # + instala el framework Synfide (workflows durables, bandeja de
# aprobaciones, kv persistente), fijado a su ultimo release:
# sha256 por archivo, synfide/VERSION registra el tag, re-ejecutar
# actualiza (solo archivos del framework — los tuyos jamas se
# pisan, y un archivo del framework que VOS editaste tampoco: la
# version nueva queda al lado como <archivo>.new con aviso fuerte),
# mas un app.syn de arranque y su suite de tests
synsema init [dir] --pwa # una app INSTALABLE (v0.6.15+): app.syn + index.html + public/{manifest.webmanifest,
# sw.js, app.js, icon.svg} + los íconos PNG generados desde el SVG + push_keys.syn
# (par VAPID para push nativo) — ver "Tu app en el teléfono"
synsema run program.syn # ejecutar (sale cuando el programa termina); `-` lee el fuente desde stdin;
# print/log/show se escriben a medida que ocurren (v0.6.29+)
synsema run program.syn -- a b # todo lo que va después de `--` es el argv propio del programa → args() == ["a", "b"]
synsema test program.syn # correr bloques `test`: archivos, directorios (recursivo) y globs, varios a la vez (v0.6.30+)
synsema build program.syn -o app # un único binario autocontenido: el engine + tu programa horneados adentro
synsema check program.syn # sin ejecutar: parse + resuelve todos los `use` + valida los templates de render("…") + (v0.6.19+) rechaza lo que serve rechazaría en un grupo `export routes` (stream/socket); UN archivo — avisos abajo
synsema code outline [path] # inteligencia de código para agentes (v0.6.13+): outline / symbol / refs / routes / caps /
# check / search / deps — estático, del parser (--json); `synsema code --mcp` sirve las
# mismas tools como el servidor MCP `synsema-code` que `init` registra en .mcp.json
synsema openapi app.syn # el /openapi.json que publicaría el server, desde el fuente — sin ejecutar,
# sin puerto (--out openapi.json, --base-url https://api.example); exit 2 sin `serve`
synsema serve program.syn # queda vivo para HTTP / crons / agentes (--watch = reinicia al cambiar un .syn)
synsema repl # REPL interactivo
synsema conform --swarm app.syn # dump de estado post-corrida (blackboard + agentes) en JSON
synsema daemon start app.syn # daemon en background (ver Deploy)
synsema llm status # config LLM resuelta + qué hay en disco + qué arquitecturas corren (--json)
synsema judge status # el slot `judge` (System One): provider, presencia de la clave, knobs (v0.6.26+)
synsema version # la versión del engine
synsema update # reemplaza este binario por el último release (avisa si ya es el último)
synsema tokens program.syn # los tokens del lexer, uno por línea — para depurar el lenguaje mismo
synsema ast program.syn # el árbol parseado — mismo propósito
Re-correr init es seguro — y repara§
init nunca decide por la mera existencia. Cada archivo que administra se clasifica por procedencia:
| Su contenido | Qué pasa |
|---|---|
| idéntico a la versión actual | ya está al día — no se toca |
| idéntico a alguna versión anterior publicada | actualizado (estaba sin ediciones tuyas) — seguía de fábrica, recibe la nueva |
| no coincide con ninguna versión publicada | es tuyo: se conserva, y la versión nueva queda al lado como <archivo>.new |
Nadie escribe a mano un archivo byte-idéntico a un release viejo, así que coincidir es prueba suficiente de que no hay trabajo tuyo que proteger. Eso es lo que permite que un proyecto que venía salteándose upgrades se ponga al día en vez de quedar congelado — y con --synfide, re-correr también repara un scaffold con archivos borrados o desfasados, incluso cuando synfide/VERSION ya nombra el último release (engine v0.5.9+; antes, existir alcanzaba para considerarlo tuyo).
init --pwa — una app instalable (engine v0.6.15+)§
synsema init [dir] --pwa genera un sitio que se instala en Android, iOS y escritorio: app.syn (el server: static "/" from "./public", la página y mount api.api), api.syn (v0.6.19+: el API como grupo export routes — /api/ping y las rutas de push), index.html, public/manifest.webmanifest, public/sw.js (service worker), public/app.js, public/icon.svg y push_keys.syn (imprime el par VAPID para .env). Los íconos PNG (icon-192.png, icon-512.png, apple-touch-icon.png) se generan desde icon.svg con el rasterizador del propio engine — editá el SVG y volvé a correr init --pwa para refrescarlos; los PNG que trajiste vos quedan intactos mientras icon.svg sea el de fábrica, y no se tocan nunca si borraste el SVG. Sin hello.syn (el starter es app.syn); los archivos base (.env.example, .gitignore, .mcp.json) vienen como siempre, con las mismas reglas de procedencia — re-correr jamás pisa lo tuyo. --pwa y --synfide son starters distintos: pasar los dos es error de uso (exit 2), igual que un flag desconocido. El directorio puede ir antes o después del flag. Recorrido: Tu app en el teléfono (PWA). Desde engine v0.6.19 el API vive en api.syn (un grupo export routes api que app.syn monta) para que las mismas rutas sirvan a una entrada de escritorio; un app.syn de fábrica de un init --pwa anterior se refresca al modular, uno editado se conserva (app.syn.new al lado).
init --desktop — la misma app como app de escritorio (engine v0.6.19+)§
synsema init [dir] --desktop escribe todo lo de --pwa más desk.syn — la entrada de escritorio: bind "127.0.0.1", mount api.api, la página renderizada con desktop: true, una ruta socket que cuenta las ventanas abiertas, el navegador abierto como ventana de app con run() bajo exec (platform() elige cmd / open / xdg-open), y shutdown() cuando se cierra la última ventana o cuando ninguna se abrió en 30 s — y public/desk.js, el socket que abre cada ventana (index.html lo carga sólo bajo { when desktop }). DESK_NO_WINDOW=1 saltea el navegador (tests, CI). synsema serve desk.syn abre la ventana; synsema build desk.syn -o desk --serve --no-console --icon public/icon.svg [--bundle] la distribuye. --desktop sobre un proyecto --pwa existente sólo agrega los dos archivos. Recorrido: Tu app en el escritorio.
Qué cubre el .env.example§
El .env.example generado viene comentado sección por sección: los pares de provider LLM (provider + SU key), los techos del host que el programa no puede subir (SYNSEMA_SPEND_CEILING y el techo por identidad SYNSEMA_SPEND_CEILING_PER_IDENTITY), los knobs de aprobación humana, el techo de tokens LLM por identidad (SYNSEMA_LLM_BUDGET_PER_IDENTITY, engine v0.6.20+), los knobs de identidad (SYNSEMA_IDENTITY, el nombre del operador para run, y SYNSEMA_IDENTITY_KEY, la semilla ed25519 del servidor que firma su Agent Card — engine v0.6.28+), los knobs del server que van solo por entorno del proceso (workers, drenado, keepalives, SYNSEMA_HEALTH_PATH, SYNSEMA_AUDIT), los knobs del recibo sellado (SYNSEMA_AUDITOR_KEY y SYNSEMA_RECEIPT_SEALED_BYTES, engine v0.6.46+, comentados) y, al final, tus propios secretos — los que nombra tu código: JWT_KEY para jwt_sign, CAPTOKEN_ROOT_KEY para captoken_mint (atenuar no necesita clave, por eso un subagente delegado nunca la ve) y AGENT_SIGNING_KEY para http_sign (que además necesita require sign("AGENT_SIGNING_KEY")). El nombre que elegís es el scope de la capability, así que agregá los tuyos con el mismo patrón. Ver Secretos e Identidad de agentes.
synsema test — archivos, directorios y globs§
synsema test app.syn # the `test` blocks of one file
synsema test tests/ # every .syn under the directory, recursively
synsema test a.syn b.syn tests/ # several paths in one run (v0.6.30+)
synsema test "tests/*.test.syn" # a glob the shell left as is (PowerShell, cmd) is expanded by synsema (v0.6.30+)
synsema test -v app.syn # also show what the program and each test print, as ` | …` lines
Antes de v0.6.30 una segunda ruta se ignoraba y corría solo la última, sin decir nada. Con varios archivos cada uno va encabezado por su ruta, y la línea de resumen los cuenta a todos. Una ruta o un glob que no encuentra ningún .syn es un error de uso (exit 2: synsema test: no .syn files in 'tests/*.nope'); un test que falla es exit 1. Los flags son los de run que tienen sentido sin argv: --flat, --sandbox / --cap-set, --deterministic, --profile, --audit, --labels, --jitless y (v0.6.46+) --trusted / --trusted-cap-set (Módulos de confianza). synsema test arma el swarm real (agentes, blackboard, señales — ver Multi-agente) y deja pasar los gates humanos para que una suite nunca se trabe en un prompt (Humano en el loop).
synsema check — errores, y los avisos que te ahorran una corrida§
synsema check archivo.syn carga sin correr: lexer, parser, nombres protegidos, cada use, cada template de render("…") y lo que serve rechazaría en un grupo export routes. Un error es exit 1. Toma un archivo — una segunda ruta no se chequea (para un proyecto entero, synsema code check).
Los avisos van a stderr y nunca cambian el exit code. Marcan código que carga pero haría otra cosa de la que dice:
| Aviso | Desde |
|---|---|
` replace_text is deprecated — use replace (the old name goes away in v1.0) ` — uno por cada uso de cualquier nombre de la lista de renombrados | v0.6.29 |
` set cfg[…] changes task touch's own copy of the argument, not the caller's value (value semantics, v0.6.29), and the task does not give it back — end the task with give cfg and call it as set x to touch(x) ` — una task escribe en un parámetro y ni lo devuelve, ni lo pasa, ni lee lo que escribió | v0.6.30 |
` set r[…] changes the loop's copy, not the item in the list — each r in rows + set r[…]` cuando la escritura se pierde | v0.6.29 |
` changed in v0.6.29: std is now the SAMPLE standard deviation (ddof = 1, like pandas) — también var, y la forma nueva [{key, items}] de group_by`; uno por llamada hasta v1.0 | v0.6.29 |
` changed in v0.6.29: an empty CSV field is nothing, so x == "" no longer finds it — use is_missing(x) ` — en un programa que lee CSV | v0.6.29 |
` changed in v0.6.29: \uXXXX in a string is now an escape — solo donde el escape se decodifica (un \u12 incompleto queda como texto literal, y también "\uD800" solo y "\u{110000}"`) | v0.6.29, acotado en v0.6.30 |
` net("api.example.com") is used by task fetch_all (line 7) but no require grants it — add require net("api.example.com") — una capacidad que el código necesita y no declara. Desde v0.6.30 los scopes de archivo se deciden con el mismo matcher del runtime: require file.read("./**") cubre read_file("x.csv")` | v0.6.19 |
un let de nivel superior o un export routes que tapa un alias de use … as; una ruta con parámetro de un segmento (GET /:x) que cubriría las URLs reservadas del descubrimiento (el runtime las sirve primero) | v0.6.20 |
` len(...) — there is no task, variable or builtin named 'len' in this file; it fails when that line runs — did you mean length(x)? — una llamada a un nombre que no existe en ningún lado: no es un builtin, y no está definido ni usado como valor en ninguna parte del archivo (without(xs, k) sin un task without pasaba check y fallaba recién al correr esa línea). Un reflejo de Python (len, str`) recibe la forma Synsema | v0.6.45 |
` fetch(url, method, headers, body, timeout) is deprecated and goes away in v1 — write fetch(url, {"method": …, "headers": …, "body": …, "timeout": …}) — un fetch cuyo segundo argumento es un texto literal o nothing` (ver Cliente HTTP) | v0.6.45 |
` http_get() takes at most two optional arguments by position; pass timeout by name (timeout = …) — the positional form goes away in v1 — un tercer argumento opcional posicional en http_get, http, http_bytes, http_post o http_put` | v0.6.45 |
` secret("K") goes in the url of http_get() but is not bound to a host, and an unbound secret only goes in a header: write require secret("K") for net("api.example.com") — un secreto en la URL, la query, el body o el form de una request (url/query/body/form`), con la línea a escribir (ver Secretos) | v0.6.46 |
` log(...) — there is no task, variable or builtin named 'log' in this file; … — did you mean ln(x) for the natural log, log10(x), log2(x) or log_base(x, b); log "message" (no parentheses) writes a log line? ` — el mismo chequeo de "nombre que no existe", con los logaritmos nombrados | v0.6.46 |
Algunos errores son errores, no avisos (exit 1, y run no arranca): un require con un nombre que no existe (v0.6.46+) — `unknown capability nett in require — did you mean require net?; a name that does not exist grants nothing. Known: net, file, … — también con un nombre con punto (require file.raed(...)) y dentro de una task o un agente; y declassify/private leídos como valor en vez de llamados por su nombre (v0.6.46+, ver Etiquetas), que synsema run y synsema code check rechazan al cargar (synsema check` todavía no lo ve).
El aviso de nombre deprecado también se imprime cuando el programa corre — una vez, por stderr, para el archivo principal y (v0.6.30+) para cada módulo cargado con use. synsema openapi deja afuera las rutas private, exactamente como el server en vivo.
synsema code — inteligencia de código para agentes§
Un agente que trabaja sobre un repo Synsema no debería leer archivos enteros para saber qué tienen. synsema code responde desde el parser — nunca ejecuta el programa ni habla con un server corriendo:
synsema code outline # cada .syn: intent, requires, imports, tasks/agentes/tipos/rutas/tests con rango de líneas
synsema code routes app.syn # la tabla HTTP que un `serve` publicaría (auth, stream/socket/proxy, expect, capabilities)
synsema code refs send_report # dónde se usa una task/agente/tipo, con el símbolo que lo contiene
synsema code caps # capabilities declaradas vs necesarias, y qué FALTA (con el `require` a agregar)
synsema code check # parse + imports + templates de todo el proyecto, con los warnings de `synsema check` (archivo:línea) y las caps faltantes; exit 1 solo con errores
synsema code search "proxy to" # búsqueda de texto en .syn/.html/.js/.css/… con el símbolo que la contiene; --regex, --kinds, --limit
synsema code deps # grafo task → task + imports
synsema code sha app.syn # el program_sha que ata un serve/run atestado (v0.6.43+; corre antes el chequeo estático; --json lista los módulos, rutas relativas con /)
Con --json sale la estructura exacta. Las mismas nueve tools las sirve por MCP synsema code --mcp, registrado como synsema-code en el .mcp.json que escribe synsema init — así cualquier agente que abra la carpeta las descubre solo. Es tooling de desarrollo sobre el código de esa carpeta, no el MCP de tu aplicación (eso es discovery: /openapi.json, /llms.txt). Referencia completa: la página code.md del skill para agentes.
code check muestra los warnings de synsema check (engine v0.6.46+), cada uno con archivo y línea, en la CLI y en la tool check por MCP: una llamada a un nombre que no existe en ningún lado, un fetch posicional, un tercer argumento opcional de http* por posición, un secret en URL/query/body/form sin atar a un host, un nombre que tapa un alias de módulo, un export que falta, una ruta paramétrica que captura URLs reservadas, y las capabilities faltantes (cuya línea es donde empieza la task, o el top level). Antes listaba solo las capabilities faltantes. --json → {ok, files, errors: [{file, line, column, message}], warnings: [igual], declassify: [...]} (declassify es el inventario completo de las llamadas a declassify del programa; el campo viejo declassify_static_only ya no está). code outline --json publica por task parameters (name, type o null, required, default cuando es literal) e input_schema (la firma como JSON Schema), con un archivo o con --full — ver Tasks § tipos en parámetros.
synsema build — un programa, un binario§
synsema build main.syn -o app produce un único ejecutable autocontenido: copia el engine en ejecución y le agrega tu programa — el .syn principal, los módulos use transitivos que importa, los templates que renderiza por un nombre literal, y todo lo que sumes con --include — sellado con un trailer y un sha256. El resultado es un archivo, como docker es un archivo; sin Python, sin npm, nada que instalar en el destino.
synsema build lamp.syn -o lamp # el binario para esta plataforma
synsema build lamp.syn -o lamp --include assets/ # empaqueta un directorio (recursivo) …
synsema build lamp.syn -o lamp --include "data/*.csv" # … o un glob de un nivel, repetible
synsema build lamp.syn -o lamp --sandbox # hornea un techo del host en el binario
synsema build lamp.syn -o lamp --cap-set "stdout,net=api.example.com" # techo horneado a medida
synsema build lamp.syn -o lamp --profile pure # hornea el perfil pure (sin filesystem/exec/db…)
synsema build lamp.syn -o lamp --deterministic # perfil pure + techo de solo stdout: sin time, sin random (v0.6.20+)
synsema build lamp.syn -o lamp --audit json # hornea el stream de audit de capacidades — json | <path> | unix:<path> (v0.6.20+)
synsema build lamp.syn -o lamp-linux --engine-binary ./synsema-linux-x86_64 # "cross": un engine donante
synsema build app.syn -o app --serve --bind 127.0.0.1 # un binario SERVER (v0.6.16+): corre el
synsema build app.syn -o app --serve --bind 0.0.0.0 --port 8080 --secure # runtime de serve con estos flags de
synsema build app.syn -o app --serve --bind 0.0.0.0 --domain app.example.com --tls-auto ops@example.com # deploy horneados
synsema build desk.syn -o desk --serve --no-console --icon icon.svg # una app de ESCRITORIO (v0.6.18+): desk.exe con tu ícono y
synsema build desk.syn -o desk --serve --icon icon.svg --bundle # sin ventana de consola; .app / dir + install.sh en macOS / Linux
Imprime built <out> (<n> files, <bytes>). El bundle es cerrado: un use cuyo path es una expresión de runtime no se puede resolver estáticamente y es un error de build, no un hueco silencioso; un --include que se escapa del directorio del programa se rechaza; los assets más grandes que el engine no son problema (el binario pesa ~30–60 MB de cualquier forma — nadie pesa la CLI de Docker).
Un server en un binario (engine v0.6.16+). Un programa con un bloque serve on sólo corre bajo el runtime de serve, así que se construye con --serve (sin el flag el build para con exit 2 y lo dice, en vez de que el binario falle al correr). El bind es obligatorio — un distribuible tiene que decir dónde escucha (127.0.0.1 para una app local, 0.0.0.0 para una pública): --bind, o la cláusula bind "…" del bloque serve (v0.6.18+; el flag le gana); --port, --domain, --tls-auto, --tls-cert/--tls-key y --secure son los mismos knobs de deploy que synsema serve, horneados (los archivos TLS se leen del disco al arrancar — la clave nunca viaja dentro del binario). Los mounts estáticos del bloque serve se empaquetan solos (static "/x" from "./public" → entra public/; un directorio que falta es error de build) y se sirven solo desde el bundle, con ETag por contenido — la app instalable entera cabe en el único archivo. Una carpeta empaquetada es parte del programa y no necesita un grant de file.read; desde el motor v0.6.45 un archivo que no está en el bundle no existe para ese mount (su fallback sale del bundle también) — ya no cae al disco. Ctrl-C / SIGTERM es el shutdown ordenado, exit 0. --serve con --profile pure se rechaza (un server bindea un socket).
Una app de escritorio en un binario (engine v0.6.18+). La cláusula bind "127.0.0.1" del bloque serve se hornea cuando no hay --bind (sin ninguno de los dos, el build para con exit 2). -o desk pasa a ser desk.exe cuando el motor que se envuelve es un ejecutable de Windows; --no-console no abre ventana de consola (stdout/stderr se descartan), y desde el motor v0.6.45 los hijos que lanza (run, proc_spawn, run_program con pipes: ssh, git, powershell) se crean con CREATE_NO_WINDOW, así ninguno abre una consola que el usuario pueda cerrar (un programa corrido desde una terminal no cambia: el hijo comparte esa consola, así ssh puede seguir pidiendo una contraseña ahí); --icon <svg|png|ico> pone tu ícono en el .exe, el .app o el lanzador de Linux; --bundle [--name "Mi App"] [--id com.example.app] escribe Mi App.app/ (macOS) o <stem>/ + install.sh (Linux) — en Windows no hay nada que hacer, y la línea built lo dice. Todo lo decide el formato del motor (PE / Mach-O / ELF), nunca la máquina que construye. La receta — el navegador como ventana de app, un socket como verdad de "hay una ventana abierta", shutdown() cuando se cierra la última — está en Tu app en el escritorio.
El binario construido corre tu programa. Todo su argv es el del programa (args()), el techo y el perfil horneados aplican, y lee su propio bundle — un read_file("assets/x") sobre un archivo empaquetado no necesita la capability file (es parte del programa, como un use), y una escritura a disco sobre un path empaquetado se rechaza. renderizar un template empaquetado tampoco necesita capability. Un nombre empaquetado se lee del bundle aunque exista un archivo con ese nombre donde corre el binario; los archivos propios del usuario salen del disco con file.read, list_dir lista el disco, y los prefijos bundle: / disk: fuerzan un lado (v0.6.20+) — mirá Archivos e I/O. cwd() es el directorio de trabajo real (con file.read(".")). --audit también se hornea, y SYNSEMA_AUDIT=json en el entorno activa el audit en cualquier binario construido.
Llegar al engine: app --engine <subcommand> corre la CLI pelada del engine dentro del binario construido — la única entrada, ya que --sandbox y compañía ahora son el argv del programa. app --engine version imprime la versión del engine; app --engine run other.syn corre otro programa. (El binario synsema de fábrica también acepta --engine como prefijo no-op, así los scripts quedan uniformes.) app --engine update se rechaza — un programa construido se reconstruye con synsema build, no se actualiza en el lugar. Un bundle manipulado (sha256 mismatch) se niega a correr.
Deployalo desde una imagen mínima con libc (gcr.io/distroless/cc-debian12, debian:*-slim — no FROM scratch: el binario Linux está enlazado contra glibc, piso 2.17 desde v0.6.21) — ver Deploy.
synsema judge status§
El mismo diagnóstico para el slot judge (Judge, v0.6.26+): provider (typesafe | laya | mock), presencia de TYPESAFE_API_KEY (nunca el valor), modelo, base URL, timeout, presupuesto y si decide lo sirve el juez (SYNSEMA_JUDGE_DECIDE), cada uno con su fuente (environ, .env, default). Cada backend muestra sólo lo que le aplica (v0.6.27+): el hosteado muestra clave, modelo, URL y timeout; el local laya muestra el checkpoint; mock, nada de eso. Offline, una línea nombra lo que falta. --json para scripts; exit 0 = vivo, 1 = offline. No toca la red.
synsema llm status§
Imprime la configuración LLM que el runtime va a usar de verdad — cada valor con su fuente (environ / .env / default), la key solo como presencia (jamás valores, prefijos ni longitudes), qué archivo .env se cargó, y un aviso si hay varios binarios synsema pisándose en el PATH. Cuando está offline nombra la variable exacta que falta — incluida la pista "hay una clave bajo DEEPSEEK_API_KEY: ¿la guardaste bajo la variable equivocada?". No toca la red. Exit 0 = vivo, 1 = offline (scripteable: synsema llm status && synsema serve app.syn).
Con el proveedor embebido además contesta "¿qué hay en esta máquina?" (v0.6.27+): los modelos que ya están en tu cache de Ollama o de Hugging Face, por nombre y con el sha de los pesos, y las arquitecturas del motor que de verdad va a correr, cada una con su origen y su sha. --json trae lo mismo bajo la clave inference, con el sha completo. Referencia completa de knobs: Config de proveedor; las arquitecturas: Inferencia local.
synsema receipt — recibos sellados para un auditor (engine v0.6.46+)§
synsema receipt keygen [--out <private key file>] # the auditor's X25519 keys
synsema receipt verify <receipt.json> # anyone: the signature (every line of a serve --receipt file)
synsema receipt open <receipt.json> --key <private | @file> [--line N] # the auditor: envelope + chain
keygenimprime{"private", "public"}(base64url, 32 bytes cada una); con--outla privada va a un archivo nuevo (no tiene que existir) y stdout trae solo{"public", "private_in"}.verify→{"ok": true, "header": {…}}(exit 0) o el motivo en stderr (exit 1); en un archivo con varios recibos,{"ok", "receipts", "bad": [{"line", "error"}]}.open→{"header", "entries": [{"seq", "sealed", "entry"}], "seed", "clock", "truncated"};--line Nelige un recibo de un archivo multilínea. Clave equivocada, cadena rota o firma mala → exit 1.- Los errores de uso salen con 2. Todo sobre el recibo — qué va al sobre, quién lo firma,
--replay, lo que no esconde: Observabilidad § Recibos sellados.
Flags útiles§
| Flag | Comando | Efecto |
|---|---|---|
--flat | run / test / conform | parsear un archivo .fsyn (documento plano) (también se autodetecta por la extensión .fsyn) |
--explain | run | reporte rico de error en stderr (contexto, call stack, sugerencias) |
-v | test | imprime además lo que imprimen el programa y cada test, cada línea indentada detrás de una barra; sin él solo se ven los resultados |
--format human|json | run | human es el default. json sin --explain: toda la corrida como un único documento JSON en stdout — {ok, output, errors, audit, exit, llm_tokens, steps} (la forma nativa del run() de wasm; la salida se junta, no se streamea; steps = sentencias ejecutadas, v0.6.20+). Con --explain: diagnósticos de error estructurados |
--provider <name> | run | forzar el proveedor LLM (anthropic/openai/minimax/deepseek) |
--sandbox | run / test / conform / serve | techo del host stdout,time — ejecutar código no confiado (ver Capacidades). Bajo serve (engine v0.6.7+) suma serve para poder bindear, y acota por igual el preámbulo, los handlers, los ticks de cron y los agentes spawneados |
--cap-set "<lista>" | run / test / conform / serve | techo del host a medida (name o name=scope, p. ej. "stdout,time,serve=8080,net=api.example.com"); --cap-set none = un techo vacío (nada, ni siquiera stdout); mutuamente excluyente con --sandbox. Ítems de v0.6.46+: declassify / declassify=<principal o glob> (con --labels, qué principales puede quitar declassify — ver Etiquetas), llm=<principal>@<host|local|> y judge=<principal>@<host|local|> (los datos privados de quién pueden llegar a qué modelo — Primitivas LLM), secret=K@api.example.com (el secreto va sólo a ese host — Secretos) |
--profile native|pure | run / test / conform / build | pure (default native) es un segundo muro: cada builtin de filesystem/exec/socket/db/cron falla con la verdad del entorno, independiente del techo — ver Sandbox § el perfil pure. serve --profile pure es un error de uso (un server bindea un socket) |
--audit json|<path>|fd:N|unix:<path> | run / test / conform / serve / build | una línea JSON por chequeo de capability — {ts, context, capability, granted, source, reason, origin, file, line} — a stderr (json), un archivo, un fd o un socket Unix (fd:/unix: solo Unix; unix: v0.6.20+), más una línea final {"summary": {granted, denied, exit}}. Los valores de secretos nunca aparecen (solo nombres). build lo hornea (v0.6.20+); SYNSEMA_AUDIT en el entorno es la forma sin flag (el flag gana). Ver Observabilidad |
--deterministic | run / test / build | (v0.6.20+) --profile pure más un techo de solo stdout — sin time, sin random: el mismo programa da la misma salida (evals reproducibles; steps() como costo). Es un alias, así que rechaza --sandbox/--cap-set/--profile native (exit 2); build lo hornea |
--labels | run / test / conform / serve | (v0.6.24+) enciende las etiquetas de flujo: private/declassify, y todo sumidero público rechaza un valor privado. Apagadas por defecto; siempre encendidas bajo serve --attested y dentro de un guest — ver Etiquetas |
--jitless | todos los comandos que corren programas | (v0.6.36+) sin nivel nativo: las tasks y los bucles calientes no se compilan a código de máquina y no se mapea memoria ejecutable; todo corre en la VM de bytecode. Mismos resultados, errores y steps() — sólo más lento en código numérico caliente. No apaga ningún chequeo. Como node --jitless; no hay variable de entorno. serve --attested lo implica. Después de -- es un argumento del programa |
--attested | serve | (v0.6.24+) una identidad P-256 atada al programa y a su configuración por un documento de attestation de la plataforma, TLS con esa clave, y GET /.well-known/attestation. Enciende --labels. Rechaza --watch (un reinicio cambiaría la identidad); si la plataforma no responde, el servidor no arranca — ver Attestation |
--attest | run | (v0.6.24+) la forma de trabajo: la salida del programa, y después una línea JSON que ata programa, entrada, salida y configuración a un documento de la plataforma. Implica --deterministic, así que rechaza --explain/--sandbox/--cap-set/--profile native (exit 2). Con --auditor (v0.6.46+) la línea JSON trae run_key y report_data suma sha256(run_key) como quinto campo — ver Atestación |
--auditor <clave pública X25519> | run / serve | (v0.6.46+) un recibo sellado para ese auditor: con --labels, reveal/sign/operaciones de wallet en una rama privada corren y sus líneas de auditoría van a un sobre HPKE de tamaño fijo (SYNSEMA_RECEIPT_SEALED_BYTES, 65536 por defecto). El recibo sale antes que la salida (stderr o --receipt); bajo serve, uno por request, y las rutas stream/socket son error de carga. SYNSEMA_AUDITOR_KEY (entorno o .env) es la forma sin bandera, también en un binario de build; gana la bandera. No con --explain. Ver Observabilidad |
--receipt <ruta|fd:N> | run / serve (con auditor) | (v0.6.46+) adónde va el recibo sellado en lugar de stderr: run reemplaza el archivo, serve agrega una línea por request. fd:N solo Unix. Si no se puede escribir → exit 2 antes de correr |
--replay <recibo abierto> | run | (v0.6.46+) vuelve a correr con la semilla y las lecturas de reloj grabadas, y se corta en la primera entrada que difiere de la corrida grabada. Toma la salida de synsema receipt open; no con --auditor/--explain/--format json/--attest. No comprueba program_sha (comparalo vos), y los efectos son reales |
--trusted <nombre>=<ruta> | run / test | (v0.6.46+, repetible) carga un módulo del host que el programa sólo puede llamar, como use "trusted:<nombre>" as t; cada task exportada corre con sus propios require, no con los de quien la llama. La ruta es relativa al directorio de trabajo y el programa nunca la ve. serve la rechaza — ver Módulos de confianza |
--trusted-cap-set "<lista>" | run / test | (v0.6.46+) el techo de todos los módulos de confianza, con la sintaxis de --cap-set. Sin él, una task de confianza desclasifica como un programa sin techo (cualquier principal) — dale uno |
--health <path> | serve | (v0.6.20+) endpoint de salud opt-in del host (GET <path> → 200 {ok, uptime_s, in_flight, engine}, sin auth ni rate limit, fuera del descubrimiento); una ruta absoluta como /healthz; lo mismo que SYNSEMA_HEALTH_PATH en el entorno; una ruta declarada en ese path gana |
--env-file <path> / --no-env-file | todos | override / desactivar la carga del .env |
--out <archivo> / --base-url <url> | openapi | escribir el documento a un archivo en vez de stdout / fijar el servers de OpenAPI (default: ninguno — el server en vivo lo deriva de domain/Host) |
--watch | serve | loop de dev: reinicia ante cualquier cambio de .syn (templates/estáticos ya se recargan por request) |
--port / --domain / --tls-auto / --bind / --secure | serve | knobs de deploy (ver Deploy) |
--trust-proxy <ip,cidr> | serve | (v0.6.42+) los proxies que tiene adelante el server, lo mismo que trust proxy [...] en el bloque: sólo de ellos se conservan los X-Forwarded-*, así ip of request y el rate limit ven al cliente real — ver Servidor HTTP |
--serve --bind <addr> [--port --domain --tls-auto --tls-cert --tls-key --secure] | build | (v0.6.16+) un binario server: el runtime de serve con estos knobs horneados; el bind es obligatorio (--bind, o la cláusula bind "…" del bloque, v0.6.18+); los mounts estáticos del serve se empaquetan solos |
--no-console / --icon <svg|png|ico> / --bundle [--name <n>] [--id <id>] | build | (v0.6.18+) escritorio: subsistema GUI (Windows), el ícono de la app (recurso del .exe / .icns / PNG del lanzador), un Nombre.app (macOS) o un dir/ + install.sh (Linux) — todo según el formato del motor; el .exe se agrega solo para un motor Windows — ver Tu app en el escritorio |
-o <archivo> / --include <p> / --engine-binary <p> | build | path de salida / empaquetar un archivo, directorio (recursivo) o glob */? (repetible) / construir contra un binario de engine donante (cross) — ver synsema build |
-- <args...> | run | fin de los flags del engine: todo lo que va después de -- es el argv propio del programa, legible con args() |
--engine <subcommand> | cualquier binario | correr la CLI pelada del engine (un prefijo no-op en synsema; la única entrada al engine de un binario synsema build) |
conform respeta --sandbox/--cap-set/--profile/--audit con el mismo significado que run (engine v0.6.14+; antes los ignoraba en silencio — el único subcomando que no hacía sandbox). Su stdout sigue siendo el contrato JSON {ok, out, err}; una denegación aparece dentro de err.
REPL§
synsema repl abre una sesión interactiva: cada línea es una sentencia top-level, y el estado persiste entre líneas (un let de una línea se ve en la siguiente). Los resultados se muestran con print/show — las expresiones peladas se evalúan pero no se ecoan:
$ synsema repl
>>> let who be "repl"
>>> show "hola " + who
hola repl
>>> print(type_of(who))
text
Salís con Ctrl+D (Ctrl+Z y Enter en Windows). También funciona no-interactivo — pipeá sentencias (printf '...' | synsema repl) para scriptear chequeos rápidos.
Programas que hablan por stdio — stdout es el resultado (motor v0.6.42+)§
Un programa cuyo stdout es su salida — un server MCP o LSP por stdio, un filtro en un pipe de la shell (synsema run x.syn | jq) — necesita sus diagnósticos en otro lado y su propio código de salida:
logva a stderr bajosynsema run, como ellogde Go y elloggingde Python. Bajo
test, serve, run --format json, --explain y --attest se junta como antes; dentro de un agente conserva el prefijo del agente. Un job que redirigía sólo stdout y esperaba ahí las líneas [LOG] ahora las encuentra en stderr (2>&1 para juntarlas).
eprint(...)esprinta stderr. Se chequea igual queprint(con--labelsrechaza datos
privados), y eprint es un nombre reservado: un programa con su propia task eprint tiene que renombrarla.
exit(code?)termina unsynsema runahí mismo con ese código (un entero de 0 a 255, 0 por
defecto; otra cosa es un error atrapable). try no lo atrapa, los agentes que siguen corriendo se detienen, y dentro de un bloque test hace fallar ese test. En un programa serve anda antes de que el servidor escuche (motor v0.6.45+): las líneas antes de serve on pueden terminar el proceso con ese código y sin error — una app de escritorio de instancia única le pide a la que ya corre que muestre su ventana y se va. Una vez que el servidor escucha es un error que indica shutdown(reason), porque cortaría los requests en curso. run --format json y el run() de @synsema/wasm lo informan como exit.
require stdin
let line be read_line()
when line == nothing
eprint("usage: echo <json> | synsema run tool.syn")
exit(2)
log "got " + text(length(line)) + " characters" -- diagnostics: stderr
print(json_encode({"ok": true, "size": length(line)})) -- the result: stdout
Exit codes§
0 al tener éxito, o el código que el programa le pasó a exit(code) (v0.6.42+); 1 ante un error de parseo, un error de runtime, o si algún agente spawneado terminó en ERROR; 2 ante un error de uso — un argumento faltante, synsema openapi sobre un archivo sin serve, synsema test sobre una ruta o un glob sin ningún .syn, o (engine v0.6.14+) un --flag desconocido, que ahora se rechaza en vez de ignorarse en silencio en run/test/conform. El run plano imprime la línea estable Runtime error: file:line:col: msg; agregá --explain para el reporte rico. (Para medir exit codes en una shell, no pipees antes de echo $? — redirigí.) Desde engine v0.6.19 una salida que nadie lee no es una falla: cuando el lector de stdout o stderr se fue (synsema run x.syn | head -1, un padre que cerró la tubería, PowerShell capturando un programa GUI al que no espera) el proceso termina en silencio con 0 — la convención de SIGPIPE — en vez de un pánico de Rust.
v0.6.14, breaking a propósito. Tres cosas por las que un programa que antes corría ahora puede frenar, cada una para mantener el techo honesto: un flag desconocido es un error (arriba); bajo un techo del host,stdoutes una capability real, así que--cap-setsinstdoutniega la salida en el primer--sandboxlo sigue incluyendo); y unrenderde un template en disco necesitarequire file.read("…")como cualquier otra lectura de archivo (los templates empaquetados no). Desde el motor v0.6.45 lo mismo vale para un{ include }o un{ layout }leído del disco: necesitafile.readsobre ese archivo.