Synsema docsENES

Capacidades e intent

Synsema es deny-by-default. Nada toca la red, el filesystem, la base de datos, los secretos ni la shell salvo que declares la capacidad con require. Si la olvidás, la operación no corre — el intérprete la rechaza, aunque el código la pida.

Esto es lo que hace seguro por construcción correr código no confiado (la salida de un LLM, un playground de docs).

capabilities.syn
-- Doc example: deny-by-default capabilities + faithful scope.
-- Uses `secret` because it proves the model with no network/disk side effects.
intent: "doc example: capabilities and intent"
require secret("APP_*")          -- name-prefix scope: covers APP_KEY, APP_DB, ... only

task read_app_key()
    -- APP_KEY is under the declared APP_* scope → allowed (still redacted, as always)
    give text(secret("APP_KEY", "demo")) == "secret(APP_KEY)"

task read_unscoped()
    -- DB_PASSWORD is NOT under APP_* → denied at the capability check (before any use)
    give secret("DB_PASSWORD")

print("APP_KEY is in scope → " + text(read_app_key()))

test "a capability you declared (in scope) is allowed"
    assert(read_app_key())

test "anything outside the declared scope is denied (deny-by-default)"
    assert_error(read_unscoped)

Declará lo que necesitás

require net("api.example.com")    -- un host
require file.read("/data/*")      -- solo lectura, bajo /data
require db("./store.db")          -- esta base de datos
require secret("STRIPE_KEY")      -- este secreto

Un fetch a cualquier host que no declaraste se bloquea. Un read_file fuera del scope se bloquea — el scope de ruta es fiel: un escape con .. se normaliza y se deniega.

Auto-otorgadas vs. a declarar

Bajo run/test, solo se auto-otorgan stdout / time / llm. Todo lo demás — net, file, db, secret, exec, serve, reveal, sign, wallet, spend, memory, y random — es deny-by-default (sí, random también: es para tokens/nonces; y memory también: el estado persistente del agente escribe archivos, así que se declara — require memory("name"), ver Memoria y estado). Bajo serve, incluso esas se exigen.

Scopes

spend — la declaración auditada de dinero

spend(monto, unidad, motivo) declara un gasto externo antes de que tu programa haga la llamada de pago real (la API de un PSP, un exchange, un builder de transacciones). No mueve dinero por sí misma — escribe PRIMERO la entrada forense del ledger y aplica el techo del host:

require spend("USD")                        -- deny-by-default SIEMPRE, scope = unidad
let total be spend(50, "USD", "reembolso orden 42")   -- → total acumulado de la unidad
print(spend_total("USD"))                   -- introspección, sin capability

Capacidades por task

Una task puede declarar su propio require más acotado — solo alcanza lo que declara, aunque el programa sea más amplio:

task fetch_orders()
    require net("api.shop.com")
    give fetch("https://api.shop.com/orders")    -- SOLO puede llegar a api.shop.com

sandbox

Un bloque sandbox despoja todas las capacidades del cuerpo no confiado que contiene — un require adentro es un no-op.

Techo del host — --sandbox / --cap-set (ejecutar código que NO confiás)

require, el scope por-task y sandbox asumen que vos escribiste el código. Cuando no fue así — correr un programa generado por un LLM, el plugin de un usuario, o un playground público — el host (quien corre synsema) impone un techo que el código no puede exceder, declare lo que declare:

synsema run  --sandbox program.syn                       # techo = stdout + time solamente
synsema run  --cap-set "stdout,db=:memory:" program.syn  # un techo a medida
synsema test --cap-set "stdout,time,random,secret" tests.syn

Tres capas — quién restringe, y cuándo:

CapaQuién restringeUsar cuando
require cap("scope")el código declara lo que necesitaconfiás en el código
bloque sandboxel código aísla una parte de sí mismoconfiás en el código
--sandbox / --cap-setel host impone un techo desde afuerano confiás en el código

--sandbox vs --cap-set:

La regla: caps_efectivas ⊆ require ∩ techo. Un require net("") bajo --cap-set "net=api.mock" obtiene nada — el código nunca sube por encima del techo. Solo resta*; los auto-grants (stdout/time/llm) también se filtran, y se propaga a los agentes spawneados y a los workers de parallel_map.

Acotá tu file/db o das de más: un --cap-set "…,file" pelado deja al código leer cualquier ruta absoluta — usá file=scratch_* (un prefijo) o db=:memory: para que solo toque lo que querés.

El playground de este sitio usa exactamente esto: cada Run/Test corre con un techo que permite cómputo, secret, SQL en memoria y archivos scratch, pero deniega exec y net real — probá un snippet con require exec(...) y tocá Run. Para un deploy público, combinalo con un contenedor de SO (defensa en profundidad).

intent

intent: "Leer datos de clientes y generar reportes"

intent es descriptivo (cualquier idioma) y se congela al arrancar — una inyección de prompt no puede ampliarlo. La seguridad viene de las capacidades, nunca de la prosa. Mirá Secretos para credenciales.