The feature
pause() — optionally with a reason string, readable afterward as
agent.pause_reason — suspends a running agent’s body mid-statement.
resume(agent) continues it from exactly that point: same local variables,
same call stack, same everything, because it’s genuinely the same
in-progress execution the whole time, not a re-run or a replayed log.
pause() only works inside a currently-running agent’s own body (found by
walking the scope chain the same way delegation and policy are — see
Sessions); calling it anywhere else raises a clear
SenseRuntimeError rather than doing something unclear.
How this works, mechanically
Each agent’s body runs on its own OS thread, and only one of the caller or the agent is ever actually executing at any instant — cooperative hand-off via twothreading.Events, not real concurrency.
1
start(agent) launches the thread and blocks
The agent’s body begins running on a new daemon thread. The calling
thread blocks on a “settled” event.
2
pause() unblocks the caller and blocks itself
Inside the agent’s thread,
pause() sets status = "paused", signals
the “settled” event (waking whichever caller was waiting), and then
blocks that same thread on a second “resume” event.3
start()/resume() return once settled
The calling thread — now unblocked — sees the agent has paused (or
completed, or failed) and returns control to whatever called
start/resume.4
resume(agent) signals the resume event and blocks again
This wakes the agent’s thread exactly where
pause() left off — Python
resumes that thread’s own call stack from inside the blocked
.wait() call, so every local variable and every level of nested
if/while/function-call the body was inside is simply still there.
The calling thread blocks on “settled” again, and the cycle repeats
until the body completes or fails.Event while the other executes — there’s no locking needed around
shared interpreter state. It’s genuinely cooperative, not preemptive: an
agent can only be paused by its own pause() call, never from outside at
an arbitrary point.
Lifecycle
pause(), or after a resume() — sets
status = "failed" and re-raises on whichever thread called
start()/resume() last, so it surfaces as an ordinary SenseError right
at that call site, not buried in a background thread.
A never-resumed agent doesn’t leak
pause() carries a value back in
resume(agent, value) takes an optional second argument — and pause()
returns it:
pause()/resume() from a pure wake-up signal into something
that can carry data across the suspend boundary — which is exactly what
makes ask_human (below) possible.
ask_human: a question/answer primitive built on this
ask_human(question) is pause(question): the
question becomes agent.pause_reason, and the return value is whatever
resume(agent, answer) provides. Outside any agent — a plain script, or the
REPL, where there’s no one to hand control back to — it falls back to
printing the question and reading a real line from stdin.
There’s deliberately no way yet to require human approval as part of an
action’s own declaration — an analogue to
requires <capability> on
action. ask_human(...) composes with .commit() by
hand today (if ask_human("approve?") == "yes": a.commit()); a first-class
“this action needs approval” marker is tracked future work, not built here.What this is not
- No real concurrency. Only one Sense thread is ever logically “live.” Two agents cannot run simultaneously — there’s no scheduler.
- No serialization. A paused agent’s state is a live Python call stack on a live OS thread. It cannot be written to disk and resumed after the process restarts. A genuinely persistent agent would need a different execution model — a step-based or bytecode interpreter whose frame state is actually serializable — not this thread-based approach.
- No inter-agent pause/resume. An agent cannot pause or resume another agent from inside its own body yet.
interpreter.py.
Continue
Agents
Back to the full agent feature set.
Tool
A capability-checked function an agent (or anything else) can call.
Architecture: agent concurrency
The implementation, for contributors and the curious.

