Synsemadocsv0.6.xENES

Operación

CLI

Un solo binario estático. 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
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` (un archivo o un directorio)
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)
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 + diagnóstico (--json para scripting)

Re-correr init es seguro — y repara§

init nunca decide por la mera existencia. Cada archivo que administra se clasifica por procedencia:

Su contenidoQué pasa
idéntico a la versión actualya está al día — no se toca
idéntico a alguna versión anterior publicadaactualizado (estaba sin ediciones tuyas) — seguía de fábrica, recibe la nueva
no coincide con ninguna versión publicadaes 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 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 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 (exit 1 con errores; warnings = caps faltantes)
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

Con --json sale la estructura exacta. Las mismas ocho 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.

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-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 desde el bundle antes que del disco, con ETag por contenido — la app instalable entera cabe en el único archivo. 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); --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.

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 directo desde FROM scratch/distroless — ver Deploy.

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). Referencia completa de knobs: Provider config.

Flags útiles§

FlagComandoEfecto
--flatrun / test / conformparsear un archivo .fsyn (documento plano) (también se autodetecta por la extensión .fsyn)
--explainrunreporte rico de error en stderr (contexto, call stack, sugerencias)
--format jsonrunsin --explain: toda la corrida como un único documento JSON en stdout — {ok, output, errors, audit, exit, llm_tokens} (la forma nativa del run() de wasm; la salida se junta, no se streamea). Con --explain: diagnósticos de error estructurados
--provider <name>runforzar el proveedor LLM (anthropic/openai/minimax/deepseek)
--sandboxrun / test / conform / servetecho 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 / servetecho 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
--profile native|purerun / test / conform / buildpure (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:Nrun / test / conform / serveuna línea JSON por chequeo de capability — {ts, context, capability, granted, source, reason, origin, file, line} — a stderr (json), un archivo, o un fd (solo Unix), más una línea final {"summary": {granted, denied, exit}}. Los valores de secretos nunca aparecen (solo nombres). Ver Observabilidad
--env-file <path> / --no-env-filetodosoverride / desactivar la carga del .env
--out <archivo> / --base-url <url>openapiescribir 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)
--watchserveloop de dev: reinicia ante cualquier cambio de .syn (templates/estáticos ya se recargan por request)
--port / --domain / --tls-auto / --bind / --secureserveknobs de deploy (ver Deploy)
--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>buildpath de salida / empaquetar un archivo, directorio (recursivo) o glob */? (repetible) / construir contra un binario de engine donante (cross) — ver synsema build
-- <args...>runfin de los flags del engine: todo lo que va después de -- es el argv propio del programa, legible con args()
--engine <subcommand>cualquier binariocorrer 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.

Exit codes§

0 al tener éxito; 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, 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, stdout es una capability real, así que --cap-set sin stdout niega la salida en el primer print (--sandbox lo sigue incluyendo); y un render de un template en disco necesita require file.read("…") como cualquier otra lectura de archivo (los templates empaquetados y los include/layout anidados no).