Declaring one doesn’t run it
agent name: ... declares an entity.
The interpreter creates a SenseAgent — identity (name), a persistent
scope (env, a child of the declaring scope, so the body closes over its
surroundings the same way a function does), and status: "created" — and
binds it, same as any other value. Nothing in the body runs yet.
start(agent) runs it
start(...) advances status to "running", executes the body, then to
"completed" (or "failed" if an unhandled error occurred) — and returns
the agent. An agent can only be started once from "created"; calling
start again is a SenseRuntimeError.
Reading state back out
import ... as m; m.some_fn uses.
.status, .name, and .pause_reason are reserved fields that always
refer to the agent itself, taking precedence over a same-named body
variable.
Agents close over their declaring scope
x to be genuinely its own, use
local
inside it, exactly as you would inside any other block.
Delegation and policy are scoped like session
agent uses the identical mechanism.
Agents can pause themselves mid-task
resume(agent, value) can also carry an
answer back into the paused body — which is exactly how
ask_human(question) works, a dedicated human-in-the-loop primitive built
on the same mechanism. It’s substantial enough to have
its own page covering how it works and what it
doesn’t cover yet.
Concurrency: async agent
A plain agent (everything above) is always sync: start()/resume()
block until the body settles. async agent makes that optional — the
caller and the agent’s body (or two async agents) can genuinely be
executing at the same wall-clock time:
1
start()/resume() skip their blocking wait
Everything else —
pause(), resuming with a value, .status/
.pause_reason — is unchanged. Only whether the caller blocks
differs.2
await(agent) gets the result when you need it
Blocks (if still
"running") until the agent settles, then applies the
exact same result-collection logic start()/resume() already use,
including re-raising the body’s error on the awaiting thread. await()
on an already-settled agent — including any sync agent, always
already settled by the time start()/resume() return — is a
harmless no-op, so it can be used uniformly regardless of sync/async.Concurrency is opt-in, only at the
agent/tool
boundary — a plain function call is never affected, and never forces a
caller to become async too (“function coloring,” the usual cost of
async-by-default languages).Communication: send / receive
Two async agents racing on a shared variable is genuinely unsafe — the
same limitation the concurrency warning above already flags. send(agent, message) / receive(timeout?) give every agent its own mailbox instead,
a thread-safe FIFO queue that exists from the moment it’s declared:
send() never blocks and works from anywhere — top-level code, another
agent’s body, even before the target has started (the message just
waits). receive() blocks the calling agent’s own thread until a
message arrives, or returns nil once an optional timeout elapses — only
legal inside a running agent’s own body, same lookup pause() uses.
Messages are delivered in send order even with several senders at once,
queue.Queue handling that safely with no new synchronization primitive
of Sense’s own. See the builtins reference
for the full signatures, and examples/agent_communication.sns in the
repo for a fuller scenario — a coordinator fanning work out to two
concurrent workers and collecting both results back.
What this doesn’t cover yet
Capability/policy enforcement on the whole body
Capability/policy enforcement on the whole body
An agent’s body can call anything the surrounding scope can, beyond
what an individual
action inside it explicitly
requires. Full agent-level sandboxing is broader future work.A true multi-agent scheduler
A true multi-agent scheduler
async agent gives real OS-thread concurrency (see above), not a
scheduler managing fairness/priority/limits across many agents — each
async call is just a fresh daemon thread. See
Agent Concurrency Model for the
mechanism.Persistence across process restarts
Persistence across process restarts
A paused agent’s state lives in a live thread’s call stack — it can’t
be serialized to disk and resumed after a restart. That would need a
genuinely different execution model (e.g. a step-based/bytecode
interpreter with serializable frames).
Budget, evaluation
Budget, evaluation
Not built. Tracked on the Roadmap.
Continue
Pause & resume
How genuine suspension works, mechanically.
Actions & policy
How an agent does something to the outside world safely.

