> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sensecode.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Pause & Resume

> How an agent genuinely suspends mid-task and continues later — including ask_human, a human-in-the-loop primitive built on the same mechanism.

## The feature

```sns theme={null}
agent approver:
    print("checking the request")
    amount = 5000
    pause("needs approval for amount over 1000: " + str(amount))
    print("approved -- proceeding")
    print("charging " + str(amount))

start(approver)
print(approver.status)          # "paused"
print(approver.pause_reason)    # "needs approval for amount over 1000: 5000"

# ... elsewhere, e.g. a human reviews and approves ...

resume(approver)
print(approver.status)          # "completed"
```

`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](/ai-native/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 two `threading.Event`s, not real concurrency.

<Steps>
  <Step title="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.
  </Step>

  <Step title="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.
  </Step>

  <Step title="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`.
  </Step>

  <Step title="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.
  </Step>
</Steps>

Because the two threads never run *simultaneously* — one is always blocked
on an `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

```txt theme={null}
created → running → (paused ⇄ running)* → completed | failed
```

An unhandled error — before any `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.

```sns theme={null}
agent broken:
    pause()
    x = 1 / 0

start(broken)      # returns normally: status is "paused"
resume(broken)      # raises SenseRuntimeError: division by zero
print(broken.status) # "failed" (if you catch the error above and check)
```

## A never-resumed agent doesn't leak

```sns theme={null}
agent a:
    pause()
    print("never reached")

start(a)
print("program exits normally, agent thread just sits blocked")
```

The agent's thread is a **daemon thread** specifically so this is safe —
the process exits normally even with paused agents nobody ever got back to.

## `pause()` carries a value back in

`resume(agent, value)` takes an optional second argument — and `pause()`
returns it:

```sns theme={null}
agent a:
    v = pause("need input")
    print(v)

start(a)
resume(a, "42")   # `v` becomes "42" once the body continues
```

This turns `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

```sns theme={null}
agent approver:
    amount = 5000
    answer = ask_human("approve charge of " + str(amount) + "? (yes/no)")
    if answer == "yes":
        print("charging " + str(amount))
    else:
        print("cancelled")

start(approver)
print(approver.pause_reason)   # the question
resume(approver, "yes")         # -> "charging 5000"
```

Inside a running agent, `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.

```sns theme={null}
name = ask_human("what is your name?")
print("hello, " + name)
```

<Info>
  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`](/safety/actions). `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.
</Info>

## What this is not

<Warning>
  This is cooperative hand-off between exactly two logical contexts at a
  time, implemented with real OS threads as a mechanism — not a general
  concurrency or scheduling system, and not a durability mechanism.
</Warning>

* **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.

See [Agent Concurrency Model](/architecture/agent-concurrency) for the
implementation in more depth, including the exact code paths in
`interpreter.py`.

## Continue

<CardGroup cols={2}>
  <Card title="Agents" icon="robot" href="/ai-native/agents">
    Back to the full agent feature set.
  </Card>

  <Card title="Tool" icon="wrench" href="/ai-native/tool">
    A capability-checked function an agent (or anything else) can call.
  </Card>

  <Card title="Architecture: agent concurrency" icon="diagram-project" href="/architecture/agent-concurrency">
    The implementation, for contributors and the curious.
  </Card>
</CardGroup>
