Syntax Reference
Complete language reference for Syntecnia — a readable, agent-aware programming language.
Getting Started
Syntecnia is an indentation-based language designed for building agentic, observable,
and human-in-the-loop programs. Programs are stored in .syn files and run
via the syntecnia CLI.
Installation
Install from PyPI:
# Install from PyPI
pip install syntecnia
Or install in editable mode directly from the repository:
# Clone, then install from repo root
git clone https://github.com/kitecosmic/Syntecnia.git
cd Syntecnia
pip install -e .
-e .) lets you modify the interpreter source and test changes immediately without reinstalling.
CLI Commands
All commands use the syntecnia executable installed by pip.
Core commands
# Run a program
syntecnia run file.syn
# Start an interactive REPL
syntecnia repl
# Type-check / static analysis without running
syntecnia check file.syn
# Print the token stream
syntecnia tokens file.syn
# Print the AST (Abstract Syntax Tree)
syntecnia ast file.syn
# Auto-generate a test scaffold
syntecnia testgen file.syn
Flags
| Flag | Description |
|---|---|
--secure flag |
Enable production / hardened security mode. Rejects any capability not explicitly declared with require. |
--provider <name> flag |
Select the LLM backend (e.g. --provider anthropic). Required for reason, decide, analyze, generate statements. |
--grant <cap> flag |
Dynamically grant a capability at launch without editing source (e.g. --grant net:host). |
--audit flag |
Emit a structured JSON audit trail of every agent action, decision, and human interaction. |
# Run in production mode with Anthropic LLM backend
syntecnia run app.syn --secure --provider anthropic
# Grant a network capability and record an audit trail
syntecnia run app.syn --grant net:host --audit
--secure in production deployments. Without it, capability checks are advisory rather than enforced.
Basics
Indentation
Syntecnia uses indentation to define blocks — there are no curly braces and no semicolons. Use 4 spaces or 1 tab per level, and be consistent within a file.
task greet(name)
when name == ""
give "Hello, stranger!"
otherwise
give "Hello, " + name + "!"
Comments
Line comments begin with --. There is no block comment syntax.
-- This is a comment
let x be 42 -- inline comment
File extensions
.syn— Standard Syntecnia source file (recommended for all programs)..fsyn— Flat/document style; see the Flat Syntax section.
Types & Values
Primitives
-- number: integers and floats (unified type)
let age be 42
let pi be 3.14
let million be 1_000_000 -- underscores for readability
-- text: UTF-8 strings
let greeting be "Hello, world!"
-- bool
let enabled be true
let offline be false
-- nothing (absence of value, like null/None)
let result be nothing
Collections
-- list: ordered, heterogeneous
let nums be [1, 2, 3]
let mixed be ["hello", 42, true]
-- map: key-value pairs (string keys)
let config be {"host": "localhost", "port": 8080}
Type coercion
-- Convert between types explicitly
let s be text(42) -- "42"
let n be number("42") -- 42.0
-- Division always returns a float
let r be 7 / 2 -- 3.5, not 3
-- Inspect type at runtime
let t be type_of(config) -- "map"
type_of(value) returns the type as a text string: "number", "text", "bool", "nothing", "list", "map", or the custom type name.
Truthiness
The following values are falsy; everything else is truthy:
falsenothing0""(empty text)[](empty list){}(empty map)
Custom types
Define structured data with type. Field types are annotated with : <type>.
Instantiate by calling the type name like a task.
type Customer
name: text
email: text
balance: number
-- Positional constructor
let alice be Customer("Alice", "alice@example.com", 500)
-- Access fields
let n be name of alice -- "Alice"
Variables
Variables are declared with let … be and mutated with set … to.
These are two distinct operations by design, making mutation explicit and auditable.
-- Declaration: introduces a new binding
let username be "Alice"
let score be 0
-- Mutation: changes an existing binding
set score to score + 10
set username to "Bob"
-- Computed values
let full_name be "Ada" + " " + "Lovelace"
set before let is a runtime error. Always declare a variable with let before attempting to mutate it with set.
set when you truly need to accumulate state across iterations or conditional branches.
Operators
Arithmetic
let a be 10 + 3 -- 13 addition
let b be 10 - 3 -- 7 subtraction
let c be 10 * 3 -- 30 multiplication
let d be 10 / 3 -- 3.333… division (always float)
let e be 10 % 3 -- 1 modulo
let f be 2 ** 8 -- 256 exponentiation
Comparison
let eq be 5 == 5 -- true
let neq be 5 != 4 -- true
let lt be 3 < 5 -- true
let gt be 5 > 3 -- true
let lte be 3 <= 3 -- true
let gte be 5 >= 4 -- true
Logical
let both be true and false -- false
let either be true or false -- true
let inv be not true -- false
String & List concatenation
-- + on text: string concatenation (coerces left side to text)
let msg be "Count: " + text(42) -- "Count: 42"
-- + on lists: concatenates two lists
let all be [1, 2] + [3, 4] -- [1, 2, 3, 4]
Pipe
The pipe operator |> passes the left-hand value as the sole argument to the right-hand task. See the Pipe Operator section for details.
let result be raw_data |> clean |> validate |> persist
Control Flow
Conditionals — when / otherwise when / otherwise
Equivalent to if / else if / else.
let score be 78
when score >= 90
print("Excellent!")
otherwise when score >= 70
print("Good job!")
otherwise
print("Keep trying!")
For loop — each … in
Iterates over any collection (list, map, or range).
let fruits be ["apple", "banana", "cherry"]
each fruit in fruits
print("I like " + fruit)
-- Accumulate with set
let total be 0
each n in [1, 2, 3, 4, 5]
set total to total + n
While loop — while
let count be 0
while count < 5
print("tick: " + text(count))
set count to count + 1
Break — stop
Use stop to exit a loop early.
each item in items
when item == "done"
stop
print(item)
Pattern matching — match … is
match evaluates a value and branches on the first is pattern that holds.
Use is true as a catch-all (like default or _).
task categorize(price)
match true
is price > 2000
give "premium"
is price > 500
give "mid-range"
is true
give "budget"
-- Match on a value directly
let status be "active"
match status
is "active" then print("Running")
is "paused" then print("Paused")
is true then print("Unknown state")
then keyword lets you write a single-expression branch on the same line as is. For multi-statement branches, use indentation on the next line instead.
Tasks (Functions)
Functions in Syntecnia are called tasks. Define them with task,
return values with give, and call them like any other language.
Basic task
task add(a, b)
give a + b
let result be add(3, 4) -- 7
Default return and void tasks
-- A task without give returns nothing implicitly
task log_event(msg)
print("[event] " + msg)
Recursion
task fib(n)
when n <= 1
give n
otherwise
give fib(n - 1) + fib(n - 2)
print(fib(10)) -- 55
Nested tasks & closures
task make_counter(start)
let count be start
task increment()
set count to count + 1
give count
give increment
let counter be make_counter(0)
print(counter()) -- 1
print(counter()) -- 2
Named parameters with as
task send_email(to as address, subject as title)
-- address and title are local aliases
print("Sending to: " + address)
give propagates out of try/recover blocks. If a give is reached inside a try, the value is returned from the enclosing task — the recover branch is bypassed.
Error Handling
Use try / recover to handle runtime errors.
The recover block receives the error message as text in the named variable.
try
let data be fetch("https://api.example.com/data")
print(data)
recover err
print("Request failed: " + err)
Nested try/recover
task safe_parse(raw)
try
let n be number(raw)
give n
recover err
log "Parse error: " + err
give 0 -- fallback value
err variable inside recover is always of type text. If you need structured error data, parse the message string or attach metadata before raising.
Security invariants
Use invariant to assert preconditions. A failed invariant is an unrecoverable error — it does not trigger recover.
invariant balance >= 0 -- crashes if violated
Pipe Operator
The pipe operator |> passes the value on the left as the first (and only)
argument to the task on the right. It chains transformations in a readable left-to-right flow.
-- These two lines are equivalent:
let result be persist(validate(clean(raw_data)))
let result be raw_data |> clean |> validate |> persist
Practical pipeline
task double(x)
give x * 2
task increment(x)
give x + 1
task square(x)
give x ** 2
-- 3 → double → 6 → increment → 7 → square → 49
let answer be 3 |> double |> increment |> square
Property Access
Syntecnia provides three equivalent forms for accessing fields on custom types and maps:
type Person
name: text
email: text
let person be Person("Ada", "ada@example.com")
-- Form 1: English-style (most readable)
let n1 be name of person
-- Form 2: Dot notation (familiar from other languages)
let n2 be person.name
-- Form 3: Bracket notation (dynamic / computed keys)
let key be "name"
let n3 be person[key]
name of x for Syntecnia-idiomatic code, dot notation when interoperating with mapped data, and bracket notation when the key is computed at runtime.
Reserved Keywords
Hard keywords are always reserved and cannot be used as identifiers.
Soft keywords are only special inside a serve block and are valid identifiers elsewhere.
Flow
Definitions
Agent coordination
Security & capabilities
Human interaction
LLM integration
Error handling
Observability
Logic
Literals
Soft keywords (serve context only)
These identifiers are only treated as keywords inside a serve block. Outside of it, they can be used freely as variable or task names.
Flat Syntax (.fsyn)
The .fsyn (flat Syntecnia) format is a document-oriented dialect where top-level
statements execute sequentially without any mandatory indented block structure. It is designed
for configuration files, data pipelines, and scripting scenarios where the program reads
more like a structured document than a traditional code file.
-- config.fsyn — flat/document style
let app_name be "MyService"
let version be "2.4.1"
let max_retries be 3
require stdout
require net:outbound
print(app_name + " v" + version)
.syn for all production application code. Reserve .fsyn for lightweight scripts, configuration declarations, and one-off document-processing pipelines where the flat structure aids readability.
The Syntecnia runtime automatically selects the appropriate parser based on the file extension.
All language features — tasks, types, control flow, error handling — remain fully available
in .fsyn files.