Skip to main content
You’ll build a “send an email” action that can’t accidentally send, can be blocked by policy, and — made reversible — can be undone. No API key needed; nothing here actually sends an email.

1. Calling an action does nothing yet

action looks like a function but behaves completely differently: calling it only prepares — the body doesn’t run until you explicitly commit.
No "SENDING to..." line printed. mail is a prepared action object, not a sent email.

2. .verify(), then .commit()

Two explicit steps stand between “prepared” and the real effect:
The body runs inside .commit() — that’s the only place the real effect happens.

3. Policy blocks it before it runs

requires email.send names the permission this action needs. policy: grants or denies it. Deny it, and .verify() refuses before anything happens — not a soft warning, a real error:

4. Make it undoable

An email can’t be unsent — that’s why send_mail above is irreversible, and Sense won’t let it declare an undo. Something that can be undone is reversible, and must declare rollback::
a.rollback() re-checks the capability before running, same as a.commit() does — a policy change between commit and rollback is caught, not ignored.

reversible or irreversible?

Ask one question: does this have a real, expressible undo? Sense enforces this at parse time in both directions — you can’t declare reversible without rollback:, and you can’t add rollback: to irreversible.

What you built

  • action — calling it only prepares; the body runs inside .commit()
  • requires <capability> + policy: allow/deny — permission checked at .verify() and again at .commit()
  • reversible action ... rollback: ... — a mandatory, real undo; .rollback() runs it

Next

Pausable Agents

Next tutorial — a runtime entity that waits on a human, mid-task.

Actions & Policy

The full reference — audit logs, approval gates, dry runs.

Ask a Model

Previous tutorial, if you missed it.

Which keyword do I want?

def vs tool vs action vs agent, decided in one glance.