Agents & coordination
Observability
-- Doc example: observability. `log` is real; `trace`/`measure`/`checkpoint` are
-- decorative markers — they RUN their body but don't persist timing/snapshots.
intent: "doc example: observability"
let answer be 0
measure "demo"
set answer to 41 + 1
print("measure ran → answer = " + text(answer))
test "measure runs its body (the timing instrumentation is decorative)"
let x be 0
measure "compute"
set x to 41 + 1
assert_eq(x, 42)Logging (real)§
log takes a full expression (it's also an expression). Under serve, log/print reach the terminal with a [serve] prefix.
log "Processing order " + order_id
logis a statement, not a function.log "msg"works;log("msg")parses withcheckbut crashes at runtime.
Logs under serve (request logging)§
serve does not write an access log for you — by default the terminal is quiet, which makes development hard. Add a log in your handlers to see traffic live:
route "GET /:lang/:version/:slug"
log "GET " + (path of request)
give render_page(...)
Each request then prints in the server's terminal as [serve] [LOG] GET /…. print works there too (same [serve] prefix). (This docs site logs its own requests this way.)
Markers — trace / measure / checkpoint (decorative)§
These run their body but the timing/snapshot instrumentation is currently a stub — they do not persist anything. trace/measure take a literal name; checkpoint takes an expression.
measure "db_query"
run_query(sql)
For real crash-resume / step tracking, use the progress builtins, not checkpoint — see Memory & state.
Capability audit (--audit json)§
Every capability check the runtime makes — granted or denied — can be streamed as JSON, one line per check, on run/test/conform/serve (engine v0.6.14+):
synsema run --audit json program.syn # to stderr
synsema run --audit ./audit.jsonl program.syn # to a file
synsema run --audit fd:3 program.syn # to a file descriptor (Unix only)
Each line is {ts, context, capability, granted, source, reason, origin, file, line}:
context— which CapabilitySet (program,agent,request,worker, asandbox:/tool:frame).origin—"program"(arequireor a call the code made) or"runtime"(an ambient grant:stdout/time/llm, orservefrom--port).reason—Granted by <grant>,No matching grant found,above host ceiling (--sandbox/--cap-set),Explicitly denied by …,auto-granted by the runtime(an ambient grant that succeeded now leaves a trace), orbundled asset (part of the program)(asynsema buildbundle read).source— the builtin that triggered the check (read_file(),run(), …), orceiling/ambient/secret-builtin.file/line— where in the program (ornull).
A final line summarizes: {"summary": {"granted": N, "denied": M, "exit": code}}. Secret values never appear — capability scopes are names (secret("STRIPE_KEY")), never the value. This is the same audit the WebAssembly run() returns as r.audit (fields on the WASM page); the native stream just adds ts/context/file/line. run_program returns its child's audit as r["audit"] — a value, not a log to parse.
Error diagnostics (opt-in)§
Plain run prints a stable one-liner (Runtime error: file:line:col: msg). For a rich report — source context, call stack, visible variables, classification, suggestions — opt in:
synsema run --explain program.syn # human-readable, on stderr
synsema run --explain --format json program.syn # structured, for tools/agents
The exit code is unchanged (1 on failure, 0 on success) either way.