Synsemadocsv0.6.xENES

Operate

CLI

One self-contained binary. The core commands:

synsema init [dir]             # scaffold: hello.syn (language tour + test), .env.example, .gitignore, .mcp.json (synsema-code)
synsema init [dir] --synfide   # + install the Synfide framework (durable workflows, approval
                               #   inbox, persistent kv), version-pinned from its latest release:
                               #   per-file sha256 verification, synfide/VERSION records the tag,
                               #   re-run to upgrade (framework files only — yours are never
                               #   overwritten, and a framework file YOU edited is kept too: the
                               #   new version lands beside it as <file>.new with a loud warning),
                               #   plus an app.syn starter and its test suite
synsema init [dir] --pwa       # an INSTALLABLE app (v0.6.15+): app.syn + index.html + public/{manifest.webmanifest,
                               #   sw.js, app.js, icon.svg} + the PNG icons generated from the SVG + push_keys.syn
                               #   (VAPID pair for native push) — see "Your app on the phone"
synsema run program.syn        # execute (exits when the program finishes); `-` reads the source from stdin;
                               #   print/log/show are written as they happen (v0.6.29+)
synsema run program.syn -- a b # everything after `--` is the program's own argv → args() == ["a", "b"]
synsema test program.syn       # run `test` blocks: files, directories (recursive) and globs, several at once (v0.6.30+)
synsema build program.syn -o app  # a single self-contained binary: the engine + your program baked in
synsema check program.syn      # no execution: parse + resolve all `use` imports + validate render("…") templates + (v0.6.19+) refuse what serve would refuse in an `export routes` group (stream/socket); ONE file — warnings below
synsema code outline [path]    # code intelligence for agents (v0.6.13+): outline / symbol / refs / routes / caps /
                               #   check / search / deps — static, from the parser (--json); `synsema code --mcp` serves
                               #   the same tools as the `synsema-code` MCP server that `init` registers in .mcp.json
synsema openapi app.syn        # the /openapi.json the server would publish, from the source — no execution,
                               #   no port (--out openapi.json, --base-url https://api.example); exit 2 without `serve`
synsema serve program.syn      # stay alive for HTTP / crons / agents (--watch = restart on .syn changes)
synsema repl                   # interactive REPL
synsema conform --swarm app.syn  # post-run state dump (blackboard + agents) as JSON
synsema daemon start app.syn   # background daemon (see Deploy)
synsema llm status             # resolved LLM config + what is on disk + which architectures run (--json)
synsema judge status           # the `judge` slot (System One): provider, key presence, knobs (v0.6.26+)
synsema version                # the engine version
synsema update                 # replace this binary with the latest release (says so when it is already the latest)
synsema tokens program.syn     # the lexer's tokens, one per line — for debugging the language itself
synsema ast program.syn        # the parsed tree — same purpose

Re-running init is safe — and it repairs§

init never decides by mere existence. Each file it manages is classified by provenance:

Its contentWhat happens
identical to the current versionya está al día — untouched
identical to any version that shipped beforeactualizado (estaba sin ediciones tuyas) — it was still factory, so it gets the new one
matches no released versionit's yours: kept, and the new version lands beside it as <file>.new

Nobody types a file byte-identical to an old release by hand, so matching one is proof enough that there's no work of yours to protect. This is what lets a project that has been skipping upgrades catch up instead of staying frozen — and with --synfide, re-running also repairs a scaffold with deleted or stale files, even when synfide/VERSION already names the latest release (engine v0.5.9+; before that, existence alone counted as ownership).

init --pwa — an installable app (engine v0.6.15+)§

synsema init [dir] --pwa scaffolds a site that installs on Android, iOS and desktop: app.syn (the server: static "/" from "./public", the page and mount api.api), api.syn (v0.6.19+: the API as an export routes group — /api/ping and the push routes), index.html, public/manifest.webmanifest, public/sw.js (service worker), public/app.js, public/icon.svg, and push_keys.syn (prints the VAPID pair for .env). The PNG icons (icon-192.png, icon-512.png, apple-touch-icon.png) are generated from icon.svg with the engine's own rasterizer — edit the SVG and re-run init --pwa to refresh them; PNGs you brought yourself are left alone when icon.svg is the factory one, and never touched if you removed the SVG. No hello.syn (the starter is app.syn); the base files (.env.example, .gitignore, .mcp.json) come as usual, with the same provenance rules — re-running never overwrites yours. --pwa and --synfide are different starters: passing both is a usage error (exit 2), as is any unknown flag. The directory may go before or after the flag. Walkthrough: Your app on the phone (PWA). Since engine v0.6.19 the API lives in api.syn (an export routes api group that app.syn mounts) so the same routes can serve a desktop entry; a factory app.syn from an older init --pwa is refreshed to the modular one, an edited one is kept (app.syn.new beside it).

init --desktop — the same app as a desktop app (engine v0.6.19+)§

synsema init [dir] --desktop writes everything --pwa writes plus desk.syn — the desktop entry: bind "127.0.0.1", mount api.api, the page rendered with desktop: true, a socket route that counts open windows, the browser opened as an app window with run() under exec (platform() picks cmd / open / xdg-open), and shutdown() when the last window closes or when no window opened in 30 s — and public/desk.js, the socket each window opens (loaded by index.html only under { when desktop }). DESK_NO_WINDOW=1 skips the browser (tests, CI). synsema serve desk.syn opens the window; synsema build desk.syn -o desk --serve --no-console --icon public/icon.svg [--bundle] ships it. --desktop on an existing --pwa project just adds the two files. Walkthrough: Your app on the desktop.

What .env.example covers§

The generated .env.example is commented section by section: the LLM provider pairs (provider + its key), the host ceilings the program can't raise (SYNSEMA_SPEND_CEILING and the per-identity SYNSEMA_SPEND_CEILING_PER_IDENTITY), the human-approval knobs, the per-identity LLM budget (SYNSEMA_LLM_BUDGET_PER_IDENTITY, engine v0.6.20+), the identity knobs (SYNSEMA_IDENTITY, the operator's name for run, and SYNSEMA_IDENTITY_KEY, the server's ed25519 seed that signs its Agent Card — engine v0.6.28+), the server knobs that live in the process environment only (workers, drain, keepalives, SYNSEMA_HEALTH_PATH, SYNSEMA_AUDIT), the sealed-receipt knobs (SYNSEMA_AUDITOR_KEY and SYNSEMA_RECEIPT_SEALED_BYTES, engine v0.6.46+, commented), and finally your own secrets — the ones your code names: JWT_KEY for jwt_sign, CAPTOKEN_ROOT_KEY for captoken_mint (attenuating needs no key, which is why a delegated sub-agent never sees it) and AGENT_SIGNING_KEY for http_sign (which also needs require sign("AGENT_SIGNING_KEY")). The name you choose is the capability scope, so add your own the same way. See Secrets and Agent identity.

synsema test — files, directories and 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

Before v0.6.30 a second path was ignored and only the last one ran, without a word. With several files each one is headed by its path, and the summary line counts them all. A path or glob that matches no .syn file is a usage error (exit 2: synsema test: no .syn files in 'tests/*.nope'); a failing test is exit 1. The flags are the ones of run that make sense without an argv: --flat, --sandbox / --cap-set, --deterministic, --profile, --audit, --labels, --jitless, and (v0.6.46+) --trusted / --trusted-cap-set (Trusted modules). synsema test wires the real swarm (agents, blackboard, signals — see Multi-agent) and lets human gates pass so a suite never blocks on a prompt (Human-in-the-loop).

synsema check — errors, and the warnings that save a run§

synsema check file.syn loads without running: lexer, parser, protected names, every use, every render("…") template and what serve would refuse in an export routes group. An error is exit 1. It takes one file — a second path is not checked (use synsema code check for a whole project).

Warnings go to stderr and never change the exit code. They flag code that loads but would do something other than what it says:

WarningSince
` replace_text is deprecated — use replace (the old name goes away in v1.0) ` — one per use of any name in the renamed listv0.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) ` — a task writes into a parameter and neither gives it back, passes it on, nor reads what it wrotev0.6.30
` set r[…] changes the loop's copy, not the item in the list — each r in rows + set r[…]` when the write is lostv0.6.29
` changed in v0.6.29: std is now the SAMPLE standard deviation (ddof = 1, like pandas) — also var, and group_by's new [{key, items}]` shape; one per call until v1.0v0.6.29
` changed in v0.6.29: an empty CSV field is nothing, so x == "" no longer finds it — use is_missing(x) ` — in a program that reads CSVv0.6.29
` changed in v0.6.29: \uXXXX in a string is now an escape — only where the escape is decoded (an incomplete \u12 stays literal text, and so do "\uD800" alone and "\u{110000}"`)v0.6.29, narrowed in 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") — a capability the code needs and does not declare. File scopes are decided with the runtime's own matcher since v0.6.30: require file.read("./**") covers read_file("x.csv")`v0.6.19
a top-level let or export routes that shadows a use … as alias; a one-segment parametric route (GET /:x) that would cover the reserved discovery URLs (the runtime serves those first)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)? — a call to a name that exists nowhere: not a builtin, and not defined or used as a value anywhere in the file (without(xs, k) with no task without passed check and failed only when that line ran). A Python reflex (len, str`) gets the Synsema formv0.6.45
` fetch(url, method, headers, body, timeout) is deprecated and goes away in v1 — write fetch(url, {"method": …, "headers": …, "body": …, "timeout": …}) — a fetch whose second argument is a text literal or nothing` (see HTTP client)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 — a third optional positional argument to http_get, http, http_bytes, http_post or 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") — a secret in the URL, the query, the body or the form of a request (url/query/body/form`), with the line to write (see Secrets)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? ` — the same "name that exists nowhere" check, with the logarithms namedv0.6.46

Some mistakes are errors, not warnings (exit 1, and run refuses to start): a require with a name that does not exist (v0.6.46+) — `unknown capability nett in require — did you mean require net?; a name that does not exist grants nothing. Known: net, file, … — also for a dotted name (require file.raed(...)) and inside a task or an agent; and declassify/private read as a value instead of called by name (v0.6.46+, see Labels), which synsema run and synsema code check refuse at load (synsema check` does not see it yet).

The deprecated-name warning is also printed when the program runs — once, on stderr, for the main file and (v0.6.30+) for each module loaded with use. synsema openapi leaves private routes out, exactly like the running server.

synsema code — code intelligence for agents§

An agent working on a Synsema repo should not read whole files to learn what is in them. synsema code answers from the parser — it never runs the program and never talks to a running server:

synsema code outline                # every .syn: intent, requires, imports, tasks/agents/types/routes/tests with line ranges
synsema code routes app.syn         # the HTTP table a `serve` would publish (auth, stream/socket/proxy, expect, capabilities)
synsema code refs send_report       # where a task/agent/type is used, with the enclosing symbol
synsema code caps                   # declared vs needed capabilities, and what is MISSING (with the `require` to add)
synsema code check                  # parse + imports + templates over the whole project, with the warnings of `synsema check` (file:line) and the missing caps; exit 1 only on errors
synsema code search "proxy to"      # text search over .syn/.html/.js/.css/… with the enclosing symbol; --regex, --kinds, --limit
synsema code deps                   # task → task call graph + imports
synsema code sha app.syn            # the program_sha an attested serve/run binds (v0.6.43+; runs the static check first; --json lists the modules, paths relative with /)

Add --json for the exact structure. The same nine tools are served over MCP by synsema code --mcp, registered as synsema-code in the .mcp.json that synsema init writes — so any agent that opens the folder discovers them. It is development-time tooling about the source in that folder, not the MCP of your application (that is discovery: /openapi.json, /llms.txt). Full reference: the code.md page of the agent skill.

code check shows the warnings of synsema check (engine v0.6.46+), each with file and line, in the CLI and in the check tool over MCP: a call to a name that exists nowhere, a positional fetch, a third optional http* argument by position, a secret in a URL/query/body/form not bound to a host, a name that shadows a module alias, a missing export, a parametric route that captures reserved URLs, and the missing capabilities (whose line is where the task, or the top level, starts). Before, it listed only the missing capabilities. --json → {ok, files, errors: [{file, line, column, message}], warnings: [same], declassify: [...]} (declassify is the complete inventory of the program's declassify calls; the old declassify_static_only field is gone). code outline --json publishes per task parameters (name, type or null, required, default when literal) and input_schema (the signature as JSON Schema), for one file or with --full — see Tasks § types on parameters.

synsema build — one program, one binary§

synsema build main.syn -o app produces a single self-contained executable: it copies the running engine and appends your program — the main .syn, the transitive use modules it imports, the templates it renders by a literal name, and whatever you add with --include — sealed with a trailer and a sha256. The result is a file, like docker is a file; no Python, no npm, nothing to install on the target.

synsema build lamp.syn -o lamp                       # the binary for this platform
synsema build lamp.syn -o lamp --include assets/     # bundle a dir (recursive) …
synsema build lamp.syn -o lamp --include "data/*.csv"  # … or a one-level glob, repeatable
synsema build lamp.syn -o lamp --sandbox             # bake a host ceiling into the binary
synsema build lamp.syn -o lamp --cap-set "stdout,net=api.example.com"   # tailored baked ceiling
synsema build lamp.syn -o lamp --profile pure        # bake the pure profile (no filesystem/exec/db…)
synsema build lamp.syn -o lamp --deterministic       # pure profile + a stdout-only ceiling: no time, no random (v0.6.20+)
synsema build lamp.syn -o lamp --audit json          # bake the capability audit stream — json | <path> | unix:<path> (v0.6.20+)
synsema build lamp.syn -o lamp-linux --engine-binary ./synsema-linux-x86_64  # "cross": a donor engine
synsema build app.syn -o app --serve --bind 127.0.0.1                  # a SERVER binary (v0.6.16+): runs the
synsema build app.syn -o app --serve --bind 0.0.0.0 --port 8080 --secure   #   serve runtime with these deployment
synsema build app.syn -o app --serve --bind 0.0.0.0 --domain app.example.com --tls-auto ops@example.com   # flags baked in
synsema build desk.syn -o desk --serve --no-console --icon icon.svg     # a DESKTOP app (v0.6.18+): desk.exe with your icon and
synsema build desk.syn -o desk --serve --icon icon.svg --bundle          #   no console window; .app / dir + install.sh on macOS / Linux

Prints built <out> (<n> files, <bytes>). The bundle is closed: a use whose path is a runtime expression can't be resolved statically and is a build error, not a silent gap; an --include that escapes the program's directory is rejected; assets larger than the engine are fine (the binary is ~30–60 MB either way — nobody weighs the Docker CLI).

A server in one binary (engine v0.6.16+). A program with a serve on block only runs under the serve runtime, so it must be built with --serve (without it the build stops with exit 2 and says so, instead of the binary failing at run time). A bind is required — a distributable must say where it listens (127.0.0.1 for a local app, 0.0.0.0 for a public one): --bind, or the serve block's bind "…" clause (v0.6.18+; the flag wins); --port, --domain, --tls-auto, --tls-cert/--tls-key and --secure are the same deployment knobs as synsema serve, baked in (TLS files are read from disk at start — the key never travels inside the binary). The static mounts of the serve block are bundled automatically (static "/x" from "./public" → public/ goes in; a missing directory is a build error) and served only from the bundle, with a content ETag — the whole installable app fits in the one file. A bundled folder is part of the program and needs no file.read grant; since engine v0.6.45 a file that is not in the bundle does not exist for that mount (its fallback comes from the bundle too) — it no longer falls back to the disk. Ctrl-C / SIGTERM is the ordered shutdown, exit 0. --serve with --profile pure is refused (a server binds a socket).

A desktop app in one binary (engine v0.6.18+). The serve block's bind "127.0.0.1" clause is baked when there is no --bind (without either, the build stops with exit 2). -o desk becomes desk.exe when the engine being wrapped is a Windows executable; --no-console opens no console window (stdout/stderr are discarded), and since engine v0.6.45 the children it starts (run, proc_spawn, run_program with pipes: ssh, git, powershell) are created with CREATE_NO_WINDOW, so none of them pops up a console the user could close (a program run from a terminal is unchanged: the child shares that console, so ssh can still ask for a password there); --icon <svg|png|ico> puts your icon in the .exe, the .app or the Linux launcher; --bundle [--name "My App"] [--id com.example.app] writes My App.app/ (macOS) or <stem>/ + install.sh (Linux) — nothing to do on Windows, and the built line says so. All of it is decided by the engine's format (PE / Mach-O / ELF), never by the building machine. The recipe — the browser as an app window, a socket as the truth of "a window is open", shutdown() when the last one closes — is in Your app on the desktop.

The built binary runs your program. Its whole argv is the program's (args()), the baked ceiling and profile apply, and it reads its own bundle — a read_file("assets/x") on a bundled file needs no file capability (it's part of the program, like a use), and a disk write to a bundled path is refused. rendering a bundled template needs no capability either. A bundled name is read from the bundle even when a file with that name exists where the binary runs; the user's own files come from the disk with file.read, list_dir lists the disk, and the bundle: / disk: prefixes force one side (v0.6.20+) — see Files & I/O. cwd() is the real working directory (with file.read(".")). --audit is baked too, and SYNSEMA_AUDIT=json in the environment turns the audit on for any built binary.

Reaching the engine: app --engine <subcommand> runs the plain engine CLI inside the built binary — the only way in, since --sandbox and friends are now the program's argv. app --engine version prints the engine version; app --engine run other.syn runs another program. (The stock synsema binary also accepts --engine as a no-op prefix, so scripts are uniform.) app --engine update is refused — a built program is rebuilt with synsema build, not updated in place. A tampered bundle (sha256 mismatch) refuses to run.

Deploy it from a minimal image with a libc (gcr.io/distroless/cc-debian12, debian:*-slim — not FROM scratch: the Linux binary is glibc-linked, floor 2.17 since v0.6.21) — see Deploy.

synsema judge status§

The same diagnosis for the judge slot (Judge, v0.6.26+): provider (typesafe | laya | mock), presence of TYPESAFE_API_KEY (never the value), model, base URL, timeout, budget and whether decide is served by the judge (SYNSEMA_JUDGE_DECIDE), each with its source (environ, .env, default). Each backend shows only what applies to it (v0.6.27+): the hosted one gets key, model, URL and timeout; the local laya gets the checkpoint; mock gets none of them. Offline, one line names what is missing. --json for scripts; exit 0 = live, 1 = offline. No network.

synsema llm status§

Prints the LLM configuration the runtime will actually use — each value with its source (environ / .env / default), key presence only (never values, prefixes, or lengths), which .env file was loaded, and a warning if several synsema binaries shadow each other in PATH. When offline it names the exact missing variable — including the "there's a key under DEEPSEEK_API_KEY: did you store it under the wrong variable name?" hint. Never touches the network. Exit 0 = live, 1 = offline (scriptable: synsema llm status && synsema serve app.syn).

With the embedded provider it also answers "what is on this machine?" (v0.6.27+): the models already in your Ollama or Hugging Face cache, by name and with the sha of the weights, and the architectures of the engine that will actually run, each with its origin and sha. --json carries the same under an inference key with the full sha. Full knob reference: Provider config; the architectures: Local inference.

synsema receipt — sealed receipts for an 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

Useful flags§

FlagCommandEffect
--flatrun / test / conformparse a .fsyn (flat document) file (also auto-detected by the .fsyn extension)
--explainrunrich error report on stderr (source context, call stack, suggestions)
-vtestalso print what the program and each test print, each line indented behind a bar; without it only the results show
--format human|jsonrunhuman is the default. json without --explain: the whole run as one JSON document on stdout — {ok, output, errors, audit, exit, llm_tokens, steps} (the native form of the wasm run(); output is collected, not streamed; steps = statements executed, v0.6.20+). With --explain: structured error diagnostics
--provider <name>runforce the LLM provider (anthropic/openai/minimax/deepseek)
--sandboxrun / test / conform / servehost ceiling stdout,time — run untrusted code (see Capabilities). Under serve (engine v0.6.7+) it adds serve so the port can bind, and caps the preamble, handlers, cron ticks and spawned agents alike
--cap-set "<list>"run / test / conform / servetailored host ceiling (name or name=scope, e.g. "stdout,time,serve=8080,net=api.example.com"); --cap-set none = an empty ceiling (nothing, not even stdout); mutually exclusive with --sandbox. v0.6.46+ items: declassify / declassify=<principal or glob> (with --labels, which principals declassify may remove — see Labels), llm=<principal>@<host|local|> and judge=<principal>@<host|local|> (whose private data may reach which model — LLM primitives), secret=K@api.example.com (the secret goes only to that host — Secrets)
--profile native|purerun / test / conform / buildpure (default native) is a second wall: every filesystem/exec/socket/db/cron builtin fails with the truth of the environment, independent of the ceiling — see Sandboxing § the pure profile. serve --profile pure is a usage error (a server binds a socket)
--audit json|<path>|fd:N|unix:<path>run / test / conform / serve / buildone JSON line per capability check — {ts, context, capability, granted, source, reason, origin, file, line} — to stderr (json), a file, an fd or a Unix socket (fd:/unix: Unix only; unix: v0.6.20+), plus a final {"summary": {granted, denied, exit}}. Secret values never appear (only names). build bakes it (v0.6.20+); SYNSEMA_AUDIT in the environment is the flag-less form (the flag wins). See Observability
--deterministicrun / test / build(v0.6.20+) --profile pure plus a ceiling of stdout only — no time, no random: the same program gives the same output (reproducible evals; steps() as the cost). An alias, so it refuses --sandbox/--cap-set/--profile native (exit 2); build bakes it
--labelsrun / test / conform / serve(v0.6.24+) turn on information-flow labels: private/declassify, and every public sink refuses a private value. Off by default; always on under serve --attested and inside a guest — see Labels
--jitlessevery command that runs programs(v0.6.36+) no native tier: hot tasks and loops are not compiled to machine code and no executable memory is mapped; everything runs in the bytecode VM. Same results, errors and steps() — only slower on hot numeric code. It turns no check off. Like node --jitless; there is no environment variable. serve --attested implies it. After -- it is an argument of the program
--attestedserve(v0.6.24+) a P-256 identity bound to the program and its configuration by a platform attestation document, TLS with that key, and GET /.well-known/attestation. Turns --labels on. Refuses --watch (a restart would change the identity); if the platform does not answer, the server does not start — see Attestation
--attestrun(v0.6.24+) the job form: the program's output, then one JSON line binding program, input, output and configuration to a platform document. Implies --deterministic, so it refuses --explain/--sandbox/--cap-set/--profile native (exit 2). With --auditor (v0.6.46+) the JSON line carries run_key and report_data gains sha256(run_key) as a fifth field — see Attestation
--auditor <X25519 public key>run / serve(v0.6.46+) a sealed receipt for that auditor: with --labels, reveal/sign/wallet ops in a private branch run and their audit lines go into a fixed-size HPKE envelope (SYNSEMA_RECEIPT_SEALED_BYTES, default 65536). The receipt comes out before the output (stderr or --receipt); under serve, one per request, and stream/socket routes are a load error. SYNSEMA_AUDITOR_KEY (environment or .env) is the flag-less form, also in a build binary; the flag wins. Not with --explain. See Observability
--receipt <path|fd:N>run / serve (with an auditor)(v0.6.46+) where the sealed receipt goes instead of stderr: run replaces the file, serve appends one line per request. fd:N Unix only. Unwritable → exit 2 before running
--replay <opened receipt>run(v0.6.46+) run again with the recorded seed and clock readings, stopping at the first input that differs from the recorded run. Takes the output of synsema receipt open; not with --auditor/--explain/--format json/--attest. It does not check program_sha (compare it yourself), and effects are real
--trusted <name>=<path>run / test(v0.6.46+, repeatable) load a module of the host's that the program can only call, as use "trusted:<name>" as t; each exported task runs with its own require, not the caller's. The path is relative to the working directory and the program never sees it. serve refuses it — see Trusted modules
--trusted-cap-set "<list>"run / test(v0.6.46+) the ceiling of every trusted module, with the syntax of --cap-set. Without it, a trusted task declassifies like a program without a ceiling (any principal) — give one
--health <path>serve(v0.6.20+) opt-in host health endpoint (GET <path> → 200 {ok, uptime_s, in_flight, engine}, no auth, no rate limit, not in discovery); an absolute path like /healthz; the same as SYNSEMA_HEALTH_PATH in the environment; a declared route at that path wins
--env-file <path> / --no-env-filealloverride / disable .env loading
--out <file> / --base-url <url>openapiwrite the document to a file instead of stdout / set OpenAPI servers (default: none — the running server derives it from domain/Host)
--watchservedev loop: restart on any .syn change (templates/statics already hot-reload per request)
--port / --domain / --tls-auto / --bind / --secureservedeployment knobs (see Deploy)
--trust-proxy <ip,cidr>serve(v0.6.42+) the proxies in front of the server, the same as trust proxy [...] in the block: only from them are X-Forwarded-* kept, so ip of request and the rate limit see the real client — see HTTP server
--serve --bind <addr> [--port --domain --tls-auto --tls-cert --tls-key --secure]build(v0.6.16+) a server binary: the serve runtime with these knobs baked in; a bind is required (--bind, or the block's bind "…" clause, v0.6.18+); the serve block's static mounts are bundled automatically
--no-console / --icon <svg|png|ico> / --bundle [--name <n>] [--id <id>]build(v0.6.18+) desktop: GUI subsystem (Windows), the app icon (.exe resource / .icns / launcher PNGs), a Name.app (macOS) or a dir/ + install.sh (Linux) — all by the engine's format; .exe is appended automatically for a Windows engine — see Your app on the desktop
-o <file> / --include <p> / --engine-binary <p>buildoutput path / bundle a file, dir (recursive) or */? glob (repeatable) / build against a donor engine binary (cross) — see synsema build
-- <args...>runend of engine flags: everything after -- is the program's own argv, readable with args()
--engine <subcommand>any binaryrun the plain engine CLI (a no-op prefix on synsema; the only way into the engine of a synsema build binary)

conform honors --sandbox/--cap-set/--profile/--audit with the same meaning as run (engine v0.6.14+; before that it silently ignored them — the one subcommand that didn't sandbox). Its stdout stays the {ok, out, err} JSON contract; a denial shows up inside err.

REPL§

synsema repl opens an interactive session: each line is a top-level statement, and state persists across lines (a let on one line is visible on the next). Results are shown with print/show — bare expressions are evaluated but not echoed:

$ synsema repl
>>> let who be "repl"
>>> show "hola " + who
hola repl
>>> print(type_of(who))
text

Exit with Ctrl+D (Ctrl+Z then Enter on Windows). It also works non-interactively — pipe statements in (printf '...' | synsema repl) to script quick checks.

Programs that speak over stdio — stdout is the result (engine v0.6.42+)§

A program whose stdout is its output — an MCP or LSP server over stdio, a filter in a shell pipe (synsema run x.syn | jq) — needs its diagnostics somewhere else and its own exit code:

test, serve, run --format json, --explain and --attest it is collected as before; inside an agent it keeps the agent's prefix. A job that redirected only stdout and expected the [LOG] lines in it now finds them on stderr (2>&1 to merge them).

private data), and eprint is a reserved name: a program with its own task eprint must rename it.

anything else is a catchable error). try does not catch it, agents still running are stopped, inside a test block it fails that test. In a serve program it works before the server listens (engine v0.6.45+): the lines before serve on can end the process with that code and no error — a single-instance desktop app asks the running instance to show its window and leaves. Once the server listens it is an error that points to shutdown(reason), since it would cut the requests in flight. run --format json and run() of @synsema/wasm report it as 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 on success, or the code a program passed to exit(code) (v0.6.42+); 1 on a parse error, a runtime error, or if any spawned agent ended in ERROR; 2 on a usage error — a missing argument, synsema openapi on a file without serve, synsema test on a path or glob with no .syn file, or (engine v0.6.14+) an unknown --flag, which is now rejected instead of silently ignored on run/test/conform. Plain run prints the stable one-liner Runtime error: file:line:col: msg; add --explain for the rich report. (For measuring exit codes in a shell, don't pipe before echo $? — redirect instead.) Since engine v0.6.19 an output nobody reads is not a failure: when stdout or stderr's reader is gone (synsema run x.syn | head -1, a parent that closed the pipe, PowerShell capturing a GUI program it does not wait for) the process ends quietly with 0 — the SIGPIPE convention — instead of a Rust panic.

v0.6.14, breaking on purpose. Three things a program that ran before may now stop on, each to keep the ceiling honest: an unknown flag is an error (above); under a host ceiling, stdout is a real capability, so --cap-set without stdout denies output at the first print (--sandbox still includes it); and a render of a disk template needs `require file.read("…")` like any other file read (bundled templates don't). Since engine v0.6.45 the same holds for a { include } or { layout } read from disk: it needs file.read on that file.