Core
Nil
Variadic. Stringifies each argument and writes a space-joined line to
stdout.
Int
Length of a
String or Array. Anything else is a SenseRuntimeError.Array<Int>
Also
range(a, b) and range(a, b, step) — same semantics as Python’s
range, materialized eagerly into an array (there’s no lazy iterator
type yet).String
Same conversion
print uses internally — the canonical stringification
of any value.Int
Converts a
String or number to Int. Raises SenseRuntimeError on an
unconvertible value.Float
Converts a
String or number to Float. Raises SenseRuntimeError on
an unconvertible value.String
Returns the runtime type name:
"Int", "Float", "String", "Bool",
"Nil", "Array", "Function", "Module", "Model", "Answer",
"Agent", "Action", "Tool" (a tool value or a remote tool from
connect_mcp), "Memory", "Skill", "McpServer",
"Future" (from calling an async tool), or "Python" (a value from
import python).Array
Appends
x to array in place and returns the same array.Nil
Raises
SenseRuntimeError with msg (default "assertion failed") if
cond is falsy.Model / Ask / Answer
See Model, Ask & Answer for the full picture.Model
One function for every provider, real or offline —
provider is
"anthropic", "openai", or "mock".For "anthropic"/"openai": model_id defaults per provider
("claude-sonnet-5" / "gpt-4o"). Optional labeled arguments after the
positional ones: temperature: (Float), max_tokens: (Int),
api_key_env: (String, defaults to "ANTHROPIC_API_KEY"/
"OPENAI_API_KEY") — the environment variable read for the key (never
a literal in Sense source). max_tokens: is the one label to write for
either provider; OpenAI’s newer models reject the older max_tokens
wire parameter outright, so OpenAIProvider sends it as
max_completion_tokens instead — invisible to Sense source either way.
Raises SenseRuntimeError immediately if
that variable isn’t set. Requires the matching optional package
(pip install sense-lang[anthropic] / [openai]).For "mock": no API key, no network, deterministic. Its second
positional argument is the name itself (defaults to "mock") rather
than a vendor model_id. Optional labeled arguments:
response_template: (String, defaults to
"(mock reasoning about: {prompt})", with {prompt} substituted from
the argument passed to ask(...)) and confidence: (Float, defaults
to 0.5). Raises SenseRuntimeError if given temperature:/
max_tokens:/api_key_env:, the same way a real provider raises if
given response_template:/confidence:.No separate name argument for real providers — pair with
model NAME = ... to name one, or the model_id
itself is used as a default name. Raises SenseRuntimeError if
provider isn’t recognized.statement
Not a function call — a real declaration keyword. Evaluates
expr,
checks it’s actually a Model (SenseTypeError otherwise), binds
NAME, and sets the model’s own .name to NAME (overriding whatever
the expression’s value carried). model x = 5 raises before x is
bound. Required, not optional: a plain x = inference(...) is a
SenseSyntaxError — set delegation = inference(...) and any other
use of the value (a bare expression, .name chained straight off the
call) are unaffected, since neither is naming a variable.Answer
Uses whichever
Model the current scope is delegated to (via
set delegation = ...). Raises SenseRuntimeError if none is
configured anywhere in the enclosing scope chain. If the delegated
model’s provider declares a capability (real providers do;
inference("mock", ...) doesn’t), checks it against the caller’s policy scope first — raises
SensePolicyError on denial, before any network call.Answer
Member access, not a top-level builtin — asks that specific
Model
directly, bypassing delegation entirely. Capability-gated the same way
ask(prompt) is.Answer
memory is a Memory value (session or persistent — both expose the
same shape). Its entire current contents are injected ahead of prompt
as context (an empty memory leaves the prompt untouched), then handled
exactly like ask(prompt) — same capability gate for a real
provider, works with inference("mock", ...) too since this is plain
string composition, not a tool-calling protocol. Raises SenseRuntimeError if
memory isn’t a Memory value. See
Memory: reasoning with memory.Answer
tools is an Array of tool and/or Skill values (never a bare
fn; an async tool raises SenseRuntimeError); every tool, bare or
inside a skill, requires a description (added to the tool declaration
as a trailing string) to be exposed to the model. A Skill flattens
into its component tools and its description is prepended to the
prompt; a tool name repeated across the given tools/skills raises
SenseRuntimeError. Capability-gated the same way ask(prompt) is,
checked before anything else. Loops (capped at 10 iterations) between
the model and the tools it requests — each requested call runs through
the exact same path a hand-written call reaches
(arity/capability/type-check/audit); a denial is fed back to the model
as the tool result, not re-raised. Raises SenseRuntimeError if the
delegated model’s provider doesn’t implement tool-calling
(inference("mock", ...)’s doesn’t). See
Tool-Calling.Skill
tools must be a non-empty Array of tool values, each already
satisfying ask_with_tools’s own requirements (not async, has a
description) — checked here, at skill(...) call time, for the
earliest possible error. No dedicated keyword, same reasoning as why
there’s no model keyword — the runtime type carries the meaning
wherever it’s checked. See
Skills.McpServer
Spawns
command args as a subprocess over MCP’s stdio transport,
performs the handshake, and discovers its tools. capability
(optional) gates every tool on the connection, checked at each
individual call. Requires the optional mcp package (pip install sense-lang[mcp]). Raises SenseRuntimeError if the process can’t be
spawned or the handshake fails (with a 30s timeout) — never hangs
indefinitely. See
MCP.Agents
See Agents and Pause & Resume.Agent
Runs the agent’s body (on its own thread). For a plain (sync)
agent,
blocks the caller until it pauses, completes, or fails, then returns the
agent, re-raising the body’s error on the calling thread if it failed.
For an async agent, returns immediately instead — the body keeps
running concurrently; use await(agent) to get the same blocking result
later. Requires status == "created".Any
Callable only from inside a currently-running agent’s own body. Suspends
it;
reason, if given, becomes agent.pause_reason. Returns whatever
the resume(agent, value?) call that wakes it up passes as value
(nil if none was given). Behavior is identical for sync and async
agents — only whether the caller of start()/resume() blocks differs.Agent
Continues a paused agent from exactly where it left off. For a sync
agent, blocks the caller until it pauses again, completes, or fails; for
an
async agent, returns immediately (use await(agent) for the
result). Requires status == "paused". value, if given, becomes that
agent’s pause() call’s return value.Nil
Puts
message on agent’s mailbox (a thread-safe FIFO queue that exists
from the moment agent name: ... is declared). Never blocks; works from
anywhere — top-level code, another agent’s body, even before the target
has been start()-ed. Raises if agent isn’t an Agent.Any
Callable only from inside a currently-running agent’s own body. Blocks
that agent’s own thread until a message arrives on its mailbox, or —
with an optional
timeout in seconds — returns nil once it elapses
instead. Messages are delivered in the order they were sent, even with
several concurrent senders.Any
Inside a running agent: equivalent to
pause(question) — suspends the
agent, sets pause_reason to question, and returns whatever
resume(agent, answer) provides. Outside any agent: prints question
and reads a real line from stdin, returning it as a String.Any
x must be an Agent or a Future. For an Agent: blocks until it
settles (a no-op if it already has — including any sync agent, which is
always already settled by the time start()/resume() return), then
returns it, re-raising its error if it failed. For a Future (from
calling an async tool): blocks until the body finishes, then returns
its value or re-raises its error. See
Agents#concurrency and
Tool#concurrency.Actions
See Actions.Action
Member access, not a top-level function — same as every step below.
These used to be free functions taking the action as an argument
(
verify(action), etc.); <action>.commit() was always a method, so
the rest moved to match it — one calling convention, not a mix. Requires
state == "prepared". Checks the action’s requires capability against
policy (raises SensePolicyError if denied). Advances state to
"verified" (or "denied").Any
Requires
state == "verified". Re-checks the capability independently
of .verify(), and — if the action declares requires approval —
requires approved == true (raises SenseApprovalError otherwise).
Then runs the action’s body and returns whatever it returns. Advances
state to "committed" (or "failed"/"denied").Action
Sets
action.approved = true. Requires state to be "prepared" or
"verified". Only meaningful for an action declared with requires approval, but harmless to call on any action.Array<String>
A simplified, human-readable line per prepare/verify/commit/deny/fail/
rolled_back/called event across every action, tool, or capability-gated
model call (e.g.
inference("anthropic", ...); inference("mock", ...)
calls are never audited) in this Interpreter, in order. In-memory only — see
Actions.Any
Runs a
reversible action’s rollback block, nested inside the exact
scope .commit()’s body ran in — sees the original args and any local
variable the commit body itself computed. Requires
action.kind == "reversible" (a
rollback block is mandatory on one, so that’s all that’s needed) and
state == "committed". Re-checks the capability the same way
.commit() does; a denial leaves state at
"committed". Advances state to "rolled_back" on success. See
Rollback.Tools
See Tool.Tool
Not a builtin function — a declaration keyword, like
fn/action.
Calling a Tool value runs its body immediately (no lifecycle); if it
declares requires <capability>, the call is checked against the
caller’s policy scope first, raising SensePolicyError on denial. An
async tool runs its body on its own thread instead and returns a
Future immediately — see await(x) under Agents above.Member access reference
Answer
Answer
.value, .confidence, .sourceModel
Model
.ask(prompt)Agent
Agent
.status, .name, .pause_reason, .doc (its docstring, nil if it
doesn’t have one — see Functions#docstrings),
plus any variable the body set in its own scope (e.g. agent.goal if
the body did goal: String = ...)Action
Action
.state, .name, .kind, .approved, .commit()Memory
Memory
Session:
.name, .kind, .remember(key, value), .recall(key),
.forget(key), .keys(). Persistent (persistent memory, in place of
.kind): .path, plus .history(key) (Array<String>, one line per
version) and .as_of(key, version) (time travel) — see
MemorySkill
Skill
.name, .description, .tools — see SkillsMcpServer
McpServer
.name, .tools (Array<Tool>), .close() — see MCPFuture
Future
.done — a non-blocking poll (true/false); use await(future) to
get the actual value — see ToolModule
Module
Any top-level binding from the imported file — see
Modules
Python
Python
Any attribute or method of the wrapped Python object, delegated
straight through — see Python Interop
Continue
Error reference
What each builtin raises, and when.
Grammar reference
The full EBNF these builtins are called from.

