Built-in Functions

Complete reference for all built-in tasks available in Syntecnia programs.

1. Core Functions

Core functions are always available — no capability declaration required. They cover output, collection operations, type conversion, and introspection.

print(values...)

Write one or more values to standard output, separated by spaces. Accepts any number of arguments.

print("Hello, World!")
print("Name:", name, "Age:", age)
print(items)          -- prints the list representation

length(collection)

Returns the number of elements in a list or map, or the number of characters in a text value.

Input typeReturns
textNumber of characters
listNumber of items
mapNumber of key-value pairs
length("hello")         -- 5
length([1, 2, 3])     -- 3
length({"a": 1})      -- 1

text(value)

Convert any value to its text representation. Integers are shown without a decimal point.

text(42)            -- "42"  (no decimal)
text(3.14)          -- "3.14"
text(true)          -- "true"
text(nothing)       -- "nothing"

number(value)

Convert a text or boolean to a number. Always returns a float.

number("42")        -- 42.0
number("3.14")      -- 3.14
number(true)        -- 1.0
number(false)       -- 0.0

append(list, item)

Returns a new list with the item added to the end. The original list is not mutated.

let nums be [1, 2, 3]
let more be append(nums, 4)
print(more)          -- [1, 2, 3, 4]

keys(map)  /  values(map)

Return a list of all keys or all values from a map, in insertion order.

let config be {"host": "localhost", "port": 8080}
print(keys(config))    -- ["host", "port"]
print(values(config))  -- ["localhost", 8080]

contains(collection, item)

Check whether an item exists in a list, or a key exists in a map, or a substring exists in text. Returns a boolean.

contains([1, 2, 3], 2)           -- true
contains({"a": 1}, "a")          -- true  (key lookup)
contains("hello world", "world")  -- true

split(text, separator)  /  join(list, separator)

split breaks a text string into a list of parts. join assembles a list of values into a single text string using the given separator.

let parts be split("a,b,c", ",")
print(parts)             -- ["a", "b", "c"]

let back be join(parts, " | ")
print(back)              -- "a | b | c"

range(end)  /  range(start, end)  /  range(start, end, step)

Generate a list of numbers. The end value is exclusive. Negative steps produce descending sequences.

range(5)              -- [0, 1, 2, 3, 4]
range(1, 6)           -- [1, 2, 3, 4, 5]
range(0, 10, 2)       -- [0, 2, 4, 6, 8]
range(5, 0, -1)       -- [5, 4, 3, 2, 1]

-- common pattern: iterate with index
each i in range(length(items))
    print(i, items[i])

type_of(value)

Returns the runtime type of a value as a text string. Useful for dynamic dispatch and debugging.

ValueReturns
42"number"
"hello""text"
true"bool"
[1,2]"list"
{"a":1}"map"
a task/function"task"
nothing"nothing"
print(type_of(42))         -- "number"
print(type_of("hi"))        -- "text"

when type_of(val) == "list"
    print("got", length(val), "items")

slice(collection, start, end?)

Return a sub-section of a list or text. start is inclusive, end is exclusive and optional (defaults to the end of the collection). Negative indices count from the end.

slice([10, 20, 30, 40], 1, 3)  -- [20, 30]
slice([10, 20, 30], 1)         -- [20, 30]
slice("hello", 1, 4)           -- "ell"
slice("world", -3)             -- "rld"

2. String Functions

Functions for text manipulation. All are pure (no capability required) and return new text values — they never mutate in place.

fmt(template, map)

Interpolate a template string using a map of named values. Placeholders use {name} syntax.

let msg be fmt("Hello {name}, you have {count} messages", {
    "name": "Alice",
    "count": 5
})
print(msg)   -- "Hello Alice, you have 5 messages"
Prefer fmt over string concatenation when building messages with multiple dynamic parts — it is more readable and easier to localize.

upper(text)  /  lower(text)

Convert a string to all upper-case or all lower-case characters.

upper("hello")    -- "HELLO"
lower("WORLD")    -- "world"

-- case-insensitive comparison
when lower(input) == "yes"
    print("confirmed")

trim(text)

Remove leading and trailing whitespace (spaces, tabs, newlines) from a text value.

trim("  hello  ")    -- "hello"
trim("\n  data\t")   -- "data"

starts_with(text, prefix)  /  ends_with(text, suffix)

Return true if the text starts with the given prefix or ends with the given suffix.

starts_with("https://example.com", "https")   -- true
ends_with("report.csv", ".csv")               -- true

let secure_urls be where(urls, task(u) starts_with(u, "https"))

replace_text(text, old, new)

Replace all occurrences of a literal substring with a new value. For pattern-based replacement, see replace_re.

replace_text("foo bar foo", "foo", "baz")   -- "baz bar baz"
replace_text("hello world", " ", "_")       -- "hello_world"

3. Regex Functions pure

Regular-expression functions require no capability. They use Python's re module under the hood.

ReDoS risk: pathological patterns (e.g. nested quantifiers like (a+)+) can cause catastrophically slow matching. Never use untrusted input as a pattern argument without sanitising it first. Untrusted text input is safe.

matches(text, pattern)

Full match — the entire text must conform to the pattern. Designed for input validation; an unanchored pattern is already implicitly anchored at both ends.

matches("12345", "[0-9]+")    -- true  (full string is digits)
matches("a 5 b", "[0-9]+")    -- false (not fully digits)

-- validate an email-like format
let valid be matches(email, "[^@]+@[^@]+\.[^@]+")
when not valid
    print("invalid email")

find_all(text, pattern)

Partial (substring) search — returns a list of all non-overlapping matches found anywhere in the text.

find_all("a1b2c3", "[0-9]")         -- ["1", "2", "3"]
find_all("cat bat rat", "[a-z]at")    -- ["cat", "bat", "rat"]
find_all("no numbers", "[0-9]+")      -- []

capture(text, pattern)

Find the first match (partial search). If the pattern has capture groups, returns a list of the group values. Without groups, returns the whole match as text. Returns nothing on no match.

-- no groups: returns the matched text
capture("order-42", "[0-9]+")           -- "42"

-- with groups: returns list of group values
capture("2025-06-18", "([0-9]{4})-([0-9]{2})-([0-9]{2})")
-- ["2025", "06", "18"]

-- no match
capture("hello", "[0-9]+")               -- nothing

replace_re(text, pattern, replacement)

Replace all matches of a regex pattern. Supports \1, \2, … backreferences to capture groups in the replacement string.

replace_re("hello world", " +", "_")
-- "hello_world"

replace_re("2025-06-18", "([0-9]{4})-([0-9]{2})-([0-9]{2})", "\3/\2/\1")
-- "18/06/2025"

replace_re("foo123bar", "[0-9]+", "NUM")
-- "fooNUMbar"

4. Intentional Operations

These functional primitives replace explicit loops. They express what you want rather than how to iterate, making code more readable, composable, and safe for concurrent execution.

Intentional operations integrate naturally with the pipe operator |>.
let result be orders
    |> where(is_active)
    |> sort_by(get_date)
    |> collect("id")

apply(function, list)

Transform every element of a list by applying a function to each one. Returns a new list of the same length.

task double(x)
    give x * 2

let doubled be apply(double, [1, 2, 3])
print(doubled)    -- [2, 4, 6]

-- inline task
let squares be apply(task(n) n * n, range(1, 6))
print(squares)    -- [1, 4, 9, 16, 25]

where(list, predicate)

Return a new list containing only the elements for which the predicate task returns true.

task is_adult(person)
    give age of person >= 18

let adults be where(users, is_adult)

-- inline
let evens be where([1,2,3,4,5], task(n) n % 2 == 0)
print(evens)    -- [2, 4]

collect(list, "property")

Extract a single named property from each map in a list (a "pluck"). The property name is passed as a text string.

let users be [
    {"name": "Alice", "age": 30},
    {"name": "Bob",   "age": 25}
]

let names be collect(users, "name")
print(names)    -- ["Alice", "Bob"]

transform(list, function, predicate?)

Like apply, but with an optional filter predicate. Elements that do not satisfy the predicate are passed through unchanged.

task apply_discount(product)
    give {...product, "price": price of product * 0.9}

task is_expensive(product)
    give price of product > 100

-- only discount products priced > 100
let discounted be transform(products, apply_discount, is_expensive)

reduce(list, function, initial)

Fold a list into a single value. The function receives the accumulator and the current element; the initial value seeds the accumulation.

task add(acc, n)
    give acc + n

let total be reduce([1, 2, 3, 4], add, 0)
print(total)    -- 10

-- sum of prices
let revenue be reduce(orders, task(acc, o) acc + amount of o, 0)

sort_by(list, key_function)

Return a new sorted list. The key function extracts the comparison value from each element. Sort is ascending by default.

task get_price(p)
    give price of p

let sorted be sort_by(products, get_price)

-- sort by text field
let by_name be sort_by(users, task(u) name of u)

group_by(list, key_function)

Partition a list into a map where keys are produced by the key function and values are lists of elements sharing that key.

let groups be group_by(orders, task(o) status of o)
-- {"pending": [...], "shipped": [...], "done": [...]}

let by_dept be group_by(employees, task(e) department of e)

find_first(list, predicate)

Return the first element that satisfies the predicate, or nothing if none matches.

let admin be find_first(users, task(u) role of u == "admin")

when admin == nothing
    print("no admin found")
otherwise
    print("admin:", name of admin)

every(list, predicate)  /  some(list, predicate)

every returns true if all elements satisfy the predicate. some returns true if at least one does. Both short-circuit.

let all_paid  be every(invoices, task(i) paid of i)
let any_late  be some(invoices,  task(i) overdue of i)

print(every([2,4,6], task(n) n % 2 == 0))   -- true
print(some([1,2,3],  task(n) n > 2))           -- true

count_where(list, predicate)

Count the elements that satisfy the predicate. Equivalent to length(where(list, predicate)) but avoids materialising the filtered list.

let active_count be count_where(users, task(u) active of u)
print(count_where([1,2,3,4], task(n) n > 2))   -- 2

flatten(list_of_lists)

Flatten one level of nesting — a list of lists becomes a single list. Does not recurse into deeper nesting.

flatten([[1,2], [3,4], [5]])   -- [1, 2, 3, 4, 5]

-- collect nested results from multiple queries
let all_tags be flatten(apply(get_tags, posts))

zip_with(list_a, list_b, combiner)

Pair corresponding elements from two lists and apply a combiner function. The result length matches the shorter list.

let names  be ["Alice", "Bob", "Carol"]
let scores be [88, 92, 77]

let records be zip_with(names, scores,
    task(n, s) {"name": n, "score": s})
-- [{"name":"Alice","score":88}, {"name":"Bob","score":92}, ...]

5. I/O Functions requires capabilities

All I/O functions require explicit capability declarations. Without the matching require statement the runtime raises a capability error before the function is called.

Declare capabilities at the top of your program or per-task for fine-grained sandboxing.

fetch(url, method?, headers?, body?) net

Make an HTTP request. Returns a map with status, headers, and body.

require net("api.example.com")

let resp be fetch("https://api.example.com/data")
print(status of resp)   -- 200
print(body   of resp)   -- raw body text

-- POST with JSON body
let r be fetch("https://api.example.com/items",
    "POST",
    {"Content-Type": "application/json"},
    '{"name":"widget"}'
)

read_file(path)  /  write_file(path, content) file

read_file returns the entire file as text. write_file creates or overwrites a file and returns true on success.

require file("/data/*")

let csv be read_file("/data/report.csv")

write_file("/data/output.txt", "processed")

list_dir(path)  /  file_exists(path) file

list_dir returns a list of filenames (not full paths) in the directory. file_exists returns a boolean.

require file("/data/*")

let files be list_dir("/data")
print(files)   -- ["report.csv", "output.txt", ...]

when file_exists("/data/config.json")
    print("config found")

run(command, args?, timeout?) exec

Execute a shell command. Returns a map with exit_code, stdout, and stderr. Optional args is a list of argument strings; optional timeout is in seconds.

require exec()

let result be run("git", ["log", "--oneline", "-5"])
print(stdout of result)

let r be run("ping", ["-c", "1", "example.com"], 5)
when exit_code of r != 0
    print("command failed:", stderr of r)

get_env(name) env

Read an environment variable by name. Returns the value as text, or nothing if the variable is not set.

require env()

let api_key be get_env("API_KEY")
when api_key == nothing
    print("API_KEY not set")

6. Time & Random requires capabilities

now() time  /  sleep(seconds) time

now() returns the current Unix timestamp as a float. sleep(seconds) pauses execution for the given duration — useful for pacing loops or SSE streams.

require time()

let start be now()
sleep(1.5)
let elapsed be now() - start
print("elapsed:", elapsed, "seconds")

format_time(timestamp, pattern?)  /  parse_time(text, pattern?) time

Format a Unix timestamp as text or parse text back to a timestamp. Default format is ISO-8601 UTC. Accepts strftime patterns for custom formatting.

require time()

-- default: ISO-8601
format_time(0)                     -- "1970-01-01T00:00:00Z"
format_time(now(), "%Y-%m-%d")     -- "2025-06-18"
format_time(now(), "%H:%M:%S")     -- "14:30:00"

-- parse back to timestamp
let ts be parse_time("2025-06-18T12:00:00Z")
let ts2 be parse_time("2025-06-18", "%Y-%m-%d")

date_parts(timestamp) time

Decompose a Unix timestamp into a map with individual date/time fields (UTC).

KeyTypeDescription
yearnumbere.g. 2025
monthnumber1–12
daynumber1–31
hournumber0–23
minutenumber0–59
secondnumber0–59
require time()

let parts be date_parts(now())
print(year   of parts)    -- 2025
print(month  of parts)    -- 6
print(day    of parts)    -- 18

random() random  /  random_int(min, max) random

random() returns a float between 0 (inclusive) and 1 (exclusive). random_int(min, max) returns an integer in the closed range [min, max].

require random()

print(random())              -- e.g. 0.7342...
print(random_int(1, 6))     -- dice roll: 1..6
print(random_int(0, 99))    -- 0..99 inclusive

7. HTTP Client Functions requires net

A full-featured HTTP client. All functions return a response map with the fields below. The net capability must be declared for the target host.

Response fieldTypeDescription
statusnumberHTTP status code
okbooltrue if status 2xx
bodytextRaw response body
jsonmap/listParsed JSON (if content-type is JSON)
headersmapResponse headers (lowercase keys)
errortext/nothingNetwork or timeout error message

http(method, url, headers?, query?, body?, timeout?)

Full control over the request. query is a map of query parameters appended to the URL. timeout is in seconds.

require net("api.example.com")

let resp be http("GET", "https://api.example.com/search",
    nothing,                            -- no extra headers
    {"q": "syntecnia", "page": 1},     -- query params
    nothing,                            -- no body
    10                                  -- 10s timeout
)
print(json of resp)

Shorthand methods

FunctionSignature
http_get http_get(url, headers?, query?)
http_post http_post(url, body, headers?)
http_put http_put(url, body, headers?)
http_delete http_delete(url, headers?)
require net("api.example.com")

-- GET with query params
let users be http_get("https://api.example.com/users",
    nothing, {"active": "true"})

-- POST JSON
let r be http_post("https://api.example.com/users",
    {"name": "Alice", "role": "admin"},
    {"Authorization": "Bearer " + token})

when not ok of r
    print("request failed:", status of r)

-- DELETE
http_delete("https://api.example.com/users/42")

8. Database Functions (SQLite) requires db

Native SQLite support. Connections are keyed by path and shared within a program. Parameterised queries prevent SQL injection — always use params for user-supplied values.

Always use parameterised queries (? placeholders + params list) for any value that comes from user input. Never interpolate values directly into a query string.

db_open(path, mode?)  /  db_close(path?)

Open a database file. mode defaults to "readwrite". Other modes: "readonly", "memory". Call db_close without arguments to close all connections.

require db("/data/store.db")

db_open("/data/store.db")
db_open("/reports/archive.db", "readonly")
db_open(":memory:", "memory")     -- in-memory DB

db_close("/data/store.db")        -- close one
db_close()                         -- close all

sql(query, params?)

Execute a SELECT query. Returns a list of row maps where each key is a column name.

require db("/data/store.db")
db_open("/data/store.db")

-- simple query
let products be sql("SELECT id, name, price FROM products")

-- with parameters
let cheap be sql(
    "SELECT * FROM products WHERE price < ? AND active = ?",
    [50, 1]
)
print(length(cheap), "results")

sql_exec(statement, params?)

Execute a data-modifying statement (INSERT, UPDATE, DELETE, CREATE TABLE, etc.). Returns a map with rows_affected and last_id.

let res be sql_exec(
    "INSERT INTO products (name, price) VALUES (?, ?)",
    ["widget", 9.99]
)
print("inserted id:", last_id of res)

sql_exec("UPDATE products SET active = 0 WHERE price > ?", [1000])
sql_exec("CREATE TABLE IF NOT EXISTS logs (id INTEGER PRIMARY KEY, msg TEXT)")

sql_batch(statement, params_list)

Execute the same statement with multiple parameter sets in a single transaction. Returns {rows_affected} as the total across all rows.

let new_items be [
    ["alpha", 10],
    ["beta",  20],
    ["gamma", 30]
]

sql_batch(
    "INSERT INTO products (name, price) VALUES (?, ?)",
    new_items
)

sql_tables()  /  paged(query, params?)

sql_tables() returns a list of table names in the open database. paged() is designed for use inside a serve route — it pushes LIMIT/OFFSET and COUNT(*) to SQL, returning a paginated envelope without loading all rows into memory.

print(sql_tables())    -- ["products", "users", "orders"]

-- inside a serve route
route "GET /products"
    give paged("SELECT id, name, price FROM products ORDER BY name")

9. Cron / Scheduling Functions

Schedule tasks to run in the background. Job names are returned so they can be cancelled later.

cron_every(seconds, task)

Register a repeating background job. The task runs every seconds seconds until cancelled. Returns the generated job name.

task check_health()
    let r be http_get("https://api.example.com/health")
    when not ok of r
        print("health check FAILED")

let job be cron_every(30, check_health)   -- every 30 seconds

cron_after(seconds, task)

Run a task once after the given delay. Returns the job name.

task send_reminder()
    print("Reminder: review the report!")

cron_after(3600, send_reminder)   -- fires once in 1 hour

cron_cancel(name)  /  cron_list()  /  cron_status()

Manage scheduled jobs. cron_cancel returns true if the job existed. cron_list() returns a list of job-info maps; cron_status() returns a formatted text summary.

cron_cancel(job)        -- stop a job by name

let jobs be cron_list()
print(length(jobs), "active jobs")

print(cron_status())

10. Agent Memory & Rules

Persistent memory, progress tracking, and owner rules are built-in primitives for AI agent programs. Memory is stored across runs; rules constrain agent behaviour.

Progress Tracking

Track long-running multi-step tasks. If an agent crashes and restarts, resume_point returns the step where it left off.

FunctionDescription
create_progress(task_name, [steps])Create a progress tracker with named steps
start_step(task_name, step_name)Mark a step as in progress
complete_step(task_name, step_name, result?)Mark a step done with optional result
fail_step(task_name, step_name, error?)Mark a step as failed
resume_point(task_name)Returns the next incomplete step, or nothing
progress_display(task_name)Formatted text summary
progress_percent(task_name)Completion percentage (0–100)
create_progress("sync", ["fetch", "validate", "update"])

start_step("sync", "fetch")
let data be fetch("https://api.example.com/items")
complete_step("sync", "fetch", text(length(data)) + " items")

start_step("sync", "validate")
-- ... if agent crashes here and restarts ...
let next be resume_point("sync")   -- "validate"

print(progress_display("sync"))
print(progress_percent("sync"), "%")

Persistent Memory

Store observations, learnings, and decisions that persist across agent runs. Memory is categorised and searchable.

CategoryUse for
preferenceUser or owner preferences
ruleOperational constraints
learningObservations made during runs
decisionDecisions made and their rationale
contextSituational information
FunctionDescription
remember(category, content, tags?)Store a memory entry; returns entry_id
recall(category?, tags?, search?)Retrieve matching entries
forget_memory(entry_id)Remove a specific memory entry
memory_summary()Formatted overview of all stored memory
-- store observations
remember("preference", "Customer prefers formal tone", ["communication"])
remember("learning",    "API is slow on Mondays",          ["api", "performance"])
remember("decision",   "Chose retry over fallback for order #42")

-- retrieve by category and tag
let prefs be recall("preference", ["communication"])
let all   be recall()    -- everything

-- search by keyword
let api_notes be recall(nothing, nothing, "API")

print(memory_summary())

Owner Rules

Rules constrain what an agent may do. They are checked against context maps at runtime and produce violations for hard blocks or warnings.

LevelEffect
mustHard block — violation stops execution
shouldWarning — logged but does not block
avoidPreference against — agent should look for alternatives
preferPreference for — used when all options are otherwise equal
FunctionDescription
add_rule(name, level, description, category?)Register a rule
check_rules(category?, context_map?)Returns a list of violation maps
get_rules(category?)Returns all registered rules
-- define rules
add_rule("max_discount", "must",   "discount <= 0.20",    "pricing")
add_rule("formal_tone",  "prefer", "Use formal tone",      "communication")
add_rule("no_pii_log",   "must",   "Never log PII data",   "privacy")

-- check before acting
let violations be check_rules("pricing", {"discount": 0.25})
when length(violations) > 0
    print("rule violation:", violations[0])

-- list rules for a category
let pricing_rules be get_rules("pricing")

Capability Summary

Functions that access external resources require a matching require declaration. Zero access by default.

Capability Syntax Functions unlocked
net require net("host.com") fetch, http, http_get, http_post, http_put, http_delete
file require file("/path/*") read_file, write_file, list_dir, file_exists
exec require exec() run
env require env() get_env
time require time() now, sleep, format_time, parse_time, date_parts
random require random() random, random_int
db require db("/path/db") db_open, db_close, sql, sql_exec, sql_batch, sql_tables, paged
serve require serve(PORT) serve on PORT, response helpers (ok, created, not_found, fail, html, render, content)
Tasks can declare their own capabilities for fine-grained sandboxing — even if the program has broader access, the task is limited to what it explicitly declares.
task fetch_orders()
    require net("api.shop.com")
    give http_get("https://api.shop.com/orders")
-- this task cannot access any other host,
-- even if the program grants net("*")