CLI
One static binary. The core commands:
synsema init [dir] # scaffold: hello.syn (language tour + test), .env.example, .gitignore
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 run program.syn # execute (exits when the program finishes)
synsema test program.syn # run `test` blocks (a file or a directory)
synsema check program.syn # parse only — no execution (fast)
synsema serve program.syn # stay alive for HTTP / crons / agents
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 + diagnosis (--json for scripting)
Re-running init is safe — and it repairs
init never decides by mere existence. Each file it manages is classified by provenance:
| Its content | What happens |
|---|---|
| identical to the current version | ya está al día — untouched |
| identical to any version that shipped before | actualizado (estaba sin ediciones tuyas) — it was still factory, so it gets the new one |
| matches no released version | it'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).
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, 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
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).
Full knob reference: Provider config.
Useful flags
| Flag | Command | Effect |
|---|---|---|
--flat | run | parse a .fsyn (flat document) file |
--explain | run | rich error report on stderr (source context, call stack, suggestions) |
--format json | run --explain | structured diagnostics for tools/agents |
--provider <name> | run | force the LLM provider (anthropic/openai/minimax/deepseek) |
--sandbox | run / test | host ceiling stdout,time — run untrusted code (see Capabilities) |
--cap-set "<list>" | run / test | tailored host ceiling (name or name=scope); mutually exclusive with --sandbox |
--env-file <path> / --no-env-file | all | override / disable .env loading |
--port / --domain / --tls-auto / --bind / --secure | serve | deployment knobs (see Deploy) |
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.
Exit codes
0 on success; 1 on a parse error, a runtime error, or if any spawned agent ended in ERROR. 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.)