Examples & Tutorials
From a two-line hello world to a multi-agent research pipeline — learn Syntecnia through working code.
1. Hello World
Every Syntecnia program starts with require declarations that declare which capabilities
the program uses. Here we pull in stdout for terminal output, then call
print to write a line.
require stdout
print("Hello, Syntecnia!")
require is a capability declaration, not an import. Syntecnia uses it to sandbox what each program can access — if you don't declare stdout, your program simply cannot write to the terminal.
2. Variables & Control Flow
Variables are declared with let … be. Conditional branching uses when
and otherwise — Python-style indentation, no curly braces required.
let name be "Alice"
let age be 30
when age >= 18
print(name + " is an adult")
otherwise
print(name + " is a minor")
when / otherwise map directly to
if / else. You can chain conditions with otherwise when for
else-if branches.
3. Working with Lists
Syntecnia treats lists as first-class values. Use where to filter and
apply to map — both accept inline task lambdas.
let numbers be [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
task is_even(n)
give n % 2 == 0
let evens be where(numbers, is_even)
let doubled be apply(task(n) give n * 2, evens)
print(doubled)
task(n) give n * 2 is an anonymous function
(lambda). You can pass it directly wherever a function is expected — no extra variable needed.
Key list functions
| Function | Description |
|---|---|
where(list, pred) | Returns items for which pred is true. |
apply(fn, list) | Applies fn to every item and returns the new list. |
collect(list, key) | Extracts a named field from each item. |
find_first(list, pred) | Returns the first matching item or nothing. |
length(list) | Returns the number of items. |
4. Pattern Matching
The match … is construct cleanly replaces long chains of
otherwise when comparisons. Each is arm is checked in order;
the first match runs.
let status be "pending"
match status
is "pending"
print("Processing...")
is "done"
print("Complete!")
is "error"
print("Failed!")
is _ arm as a catch-all default.
Future versions of Syntecnia will warn when a match on a known type is
non-exhaustive.
5. Error Handling
Network calls and I/O can fail. Wrap risky operations in a try block and handle
failures in recover. The error value is bound to the name after recover.
require net("api.example.com")
try
let data be fetch("https://api.example.com/data")
print(data)
recover err
print("Request failed: " + err)
require net("api.example.com") grants
the program permission to reach that hostname. Attempting a fetch to an
undeclared host raises a capability error at runtime before any network traffic is sent.
6. Pipe Operator
The pipe operator |> passes the result of the left expression as the first
argument to the function on the right, enabling readable left-to-right data pipelines without
deep nesting.
let result be raw_data |> clean |> validate |> transform |> format
This is equivalent to the nested call:
let result be format(transform(validate(clean(raw_data))))
7. Custom Types
Declare structured data with type. Fields are annotated with their primitive types
(text, number, bool). Access fields using the
of keyword.
type Product
name: text
price: number
stock: number
let p be Product("Widget", 9.99, 100)
print(name of p + " costs $" + text(price of p))
name of p reads the name field
from instance p. This deliberate English phrasing avoids the dot-access
ambiguity found in languages that overload . for both method calls and
property access.
8. Simple API Server
Syntecnia can serve HTTP routes directly. Declare the port with require serve(port),
declare a database path with require db(path), then define routes inside a
serve on block. Each route arm handles a method-path pair.
intent: "Product catalog API"
require serve(3000)
require db("./products.db")
db_open("./products.db")
sql_exec("CREATE TABLE IF NOT EXISTS products (id INTEGER PRIMARY KEY, name TEXT, price REAL)")
serve on 3000
route "GET /products"
give sql("SELECT * FROM products")
route "GET /products/:id"
let id be params.id
let results be sql("SELECT * FROM products WHERE id = ?", [id])
when length(results) == 0
give not_found("Product not found")
otherwise
give results
route "POST /products"
expect body {name: text, price: number}
let b be json of request
sql_exec("INSERT INTO products (name, price) VALUES (?, ?)", [b.name, b.price])
give created({"status": "created"})
expect body {…} declares the expected shape
of the request body. Syntecnia rejects requests that don't match with a 400 response
automatically — no manual validation boilerplate needed.
sql / sql_exec. Syntecnia uses prepared
statements under the hood, preventing SQL injection.
9. Blog with Content Negotiation
The content(page(…)) pattern lets a single route serve HTML to browsers,
Markdown to AI agents, and JSON to API clients — Syntecnia inspects the
Accept header and renders accordingly.
intent: "Agent-friendly blog"
require serve(8080)
require db("./blog.db")
require time
db_open("./blog.db")
describe
about: "A blog that serves HTML, Markdown, and JSON"
api: "GET /blog for all posts, GET /blog/:slug for individual"
serve on 8080
route "GET /blog"
let posts be sql("SELECT * FROM posts ORDER BY created DESC")
give content(page(
[heading(1, "Blog"), list(collect(posts, "title"))],
{"title": "Blog"}
))
route "GET /blog/:slug"
let post be find_first(sql("SELECT * FROM posts"), task(p) give p.slug == params.slug)
when post == nothing
give not_found("Post not found")
give content(page(
[heading(1, post.title), prose(post.body)],
{"title": post.title}
))
describe block is read by AI agents to understand what
the API does. Together with intent, it makes your server self-documenting to
both humans and LLMs — no OpenAPI spec required.
10. Multi-Agent Research System
Syntecnia has first-class support for multi-agent programs. Each agent block
declares its own capabilities, communicates via share / observe,
and coordinates with signal / wait_for.
intent: "Research and report on a topic"
require net("*.wikipedia.org")
require llm
require stdout
agent Researcher
require net("*.wikipedia.org")
task run(topic)
let data be fetch("https://en.wikipedia.org/api/rest_v1/page/summary/" + topic)
share data as "research"
signal "research_done"
agent Analyst
require llm
wait_for "research_done"
observe "research" as data
let analysis be analyze data for "key insights"
share analysis as "analysis"
signal "analysis_done"
agent Reporter
require llm
wait_for "analysis_done"
observe "analysis" as insights
let report be generate "summary report" given insights
share report as "final_report"
spawn Researcher with topic = "Artificial_intelligence"
spawn Analyst
spawn Reporter
Researcher can access the network but not the LLM; Analyst can
use the LLM but has no network access. This principle-of-least-privilege design is
enforced at the runtime level.
share … as "key" writes a named value into
a shared workspace. observe "key" as name reads it — and blocks until the
value is available if it hasn't been written yet.
11. Landing Page with Templates
Use render(template, data) to render HTML templates from the filesystem.
Serve static assets (images, CSS, JS) from a directory with static.
The data object supports nested lists and HTML fragments.
intent: "Marketing landing page"
require serve(3000)
serve on 3000
static "/assets" from "./public"
route "GET /"
give render("home.html", {
"title": "My Startup",
"tagline": "Building the future",
"features": [
{"name": "Fast", "desc": "Lightning speed"},
{"name": "Secure", "desc": "Enterprise security"},
{"name": "Simple", "desc": "Easy to use"}
],
"highlighted": true,
"note": "<em>Now in beta!</em>"
})
render are
HTML-escaped by default. To pass raw HTML (as with "note" above), the
template must explicitly opt in using an unescaped interpolation tag. This prevents
accidental XSS from user-generated content.
12. Human-in-the-Loop Agent
Some decisions should not be fully automated. Syntecnia provides
approve, confirm, and ask to pause execution and
request human input before taking irreversible actions.
intent: "Process refunds with human approval"
require db("./orders.db")
require llm
require stdout
let order be sql("SELECT * FROM orders WHERE id = ?", [order_id])
let recommendation be decide between ["full_refund", "partial_refund", "deny"] given order
when recommendation == "full_refund"
approve "Issue full refund of $" + text(order.amount) + "?"
sql_exec("UPDATE orders SET status = 'refunded' WHERE id = ?", [order_id])
otherwise when recommendation == "partial_refund"
let amount be ask "Partial refund amount?"
confirm "Refund $" + amount + " to customer?"
sql_exec("UPDATE orders SET status = 'partial_refund' WHERE id = ?", [order_id])
otherwise
log "Refund denied for order " + text(order_id)
approve or
confirm prompt, the program halts at that point — subsequent statements
(including the database write) do not execute. Design your flows so that dangerous
side-effects always come after the checkpoint.
approve "message" shows a yes/no gate;
confirm "message" is a second confirmation for high-stakes actions;
ask "prompt" collects free-form input and returns it as text.