Counter your priors
No LLM was trained on Synsema, so a model writing Synsema falls back on habits from other
languages. These are the differences that bite. **Every claim here is verified against the
engine** (the ones below are a passing doctest):
Coming from Python specifically? Start with Python → Synsema — the translation table: it maps the Python reflexes (def/return, f-strings, comprehensions,try/except,d.get) to the Synsema forms in one place. This page collects the rest of the engine-verified gotchas.
-- Doc example: places where Synsema differs from what a fresh LLM assumes.
-- Every claim below is a passing assertion against the engine (doctested).
intent: "doc example: counter-your-priors gotchas"
require secret("API_KEY")
print("1 / 2 = " + text(1 / 2) + " (division is always float)")
test "division is ALWAYS float (1/2 is 0.5, not 0)"
assert_eq(1 / 2, 0.5)
assert_eq(type_of(1 / 2), "number")
test "there is no has(); use contains() for map keys"
let m be {"a": 1, "b": 2}
assert(contains(m, "a"))
assert(not contains(m, "z"))
test "concatenating text + secret redacts the WHOLE string (prefix absorbed)"
let k be secret("API_KEY", "sk-real-123")
assert_eq(type_of("Bearer " + k), "secret")
assert_eq(text("Bearer " + k), "secret(API_KEY)")
test "property access uses `of` (or dot)"
let cfg be {"host": "localhost", "port": 8080}
assert_eq(host of cfg, "localhost")
More gotchas (verified)
logis a statement, not a function. Writelog "message", notlog("message")—
the function form parses with check but crashes at runtime (Undefined variable: log).
random()/random_int()needrequire randomeven underrun— it is not
auto-granted (random is for tokens/nonces, so it's deny-by-default).
applyorder is not a trap anymore — the intentional family (apply/where/
transform/reduce/sort_by/group_by/find_first/every/some/count_where/
zip_with) accepts both (fn, list, …) and (list, fn, …); two tasks or two
lists where one-and-one is expected is an explicit error, never a guess. index_of
returns nothing when absent (not -1 — check when idx != nothing).
a of b.creads asa of (b.c)— for(a of b).c, bind first:let x be a of b,
then x.c (the runtime error carries this hint now). And request/query/params
exist only inside route handlers — pass them to helper tasks as parameters.
- Agent memory must be DECLARED —
remember/recall/rules/progress fail with
Capability not granted: memory unless the program has require memory("<name>") at the
top (not auto-granted, even under run: it writes files). The declared name — not the
filename — keys the .db; see Memory & state.
recall(...)is newest-first —[0]is the most recent entry — and truncates to 200
results (pass a 5th argument to raise the limit). Inside an agent, recall() reads only
that agent's own entries by default — cross namespaces with recall(from = "other") or
from = "*".
- LLM ops take a subject keyword:
reason about X,decide between [...] given X,
analyze X for "...", generate "..." given X. A bare reason "literal" only became
valid in a recent fix.
- A
:paramroute silently drops a.md/.jsonsuffix (it's content negotiation).
To detect the suffix, read path of request — the param itself is already stripped.
- Declared routes always beat
staticmounts. With wildcard routes like
GET /:lang/:version, a static "/assets" mount is never reached — serve assets with a
declared GET /assets/*path route instead.
- Division is always float:
1 / 2is0.5, andtype_of(1 / 2)is"number". printbuffers underrun— for a live REPL useflush()/read_line. Under
serve, print/log reach the terminal with a [serve] prefix.
- The lexer rejects a UTF-8 BOM, and a
use "..."import cannot escape its directory
(../x.syn is denied).
If a model keeps generating one of these wrong, copy this page into its context — it's exactly the prior that needs correcting.