Operate
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 # no execution: parse + resolve all `use` imports + validate render("…") templates
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 + 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 Agent identity.
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 |
--watch | serve | dev loop: restart on any .syn change (templates/statics already hot-reload per request) |
--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.)