Approvals

Human-in-the-loop for irreversible tool calls. What triggers them, how to clear the queue, email notifications.

Approvals are Tenet's pause-button for irreversible actions. When your agent tries to call a tool we've classified irreversible (github.repos.delete, stripe.refunds.create, etc.), tenet.execute() returns ESCALATE instead of running the call. A human approves or rejects the action in the dashboard; only after approval does the downstream call execute.

What triggers an escalation

  • A locked service. Every newly connected service starts Locked: every call escalates until you relax the trust level at /protections. If you're seeing escalations on read-only calls, this is why.
  • Any tool classified as irreversible in the built-in catalog — at every trust level, even Trusted; see the policies guide.
  • Stripe charges or payment_intents over $100.
  • A per-function override you've set to escalate.

You can't edit which tools are classified irreversible, but you can set per-function overrides (allow / escalate / block) at /protections; see the current classification there.

The approval flow

Your agent calls tenet.execute() →
  Engine evaluates → outcome: ESCALATE →
  Review row created at /reviews →
  Email sent to your notification email (if email_mode = per_event) →
  [you approve in dashboard] →
  Pending execution fires the original call →
  Audit log gets the final decision

The execute response on ESCALATE looks like:

{
  "decisionId": "dec_abc...",
  "outcome": "ESCALATE",
  "reviewId": "rev_xyz...",
  "reasonCodes": ["irreversible-tool"]
}

Your agent should NOT proceed as if the call ran. You can poll /v1/executions/{requestId} for the final state, or just rely on the email/dashboard for the human to act.

Approving / rejecting

Open /reviews in the dashboard. Each pending row shows:

  • The tool being called and its arguments
  • The agent making the call
  • The risk score (if calculated)
  • The reason codes (why this policy fired)

Click Approve: the call runs with your downstream credentials, returns the result, and the audit log gets updated. Click Reject: the call is permanently refused, no side effects.

Email notifications

By default, escalations trigger emails to your notification email (set at /settings; falls back to your account email) with a 30-minute rate limit. Three modes at /settings:

  • Per-event (default): first escalation in a 30-min window sends immediately. Subsequent events in the window batch into a digest at the next 30-min mark.
  • Digest only: suppress immediate emails; only the 30-min digest fires.
  • Off: no emails. You'll need to check /reviews manually.

Emails currently link to /reviews for action. One-click approve/reject from email is not yet available (it requires signed magic-link tokens we haven't built yet).

Concurrency

If your agent makes multiple irreversible calls in parallel, each gets its own review row. They're independent; approving one doesn't approve the others. The agent has to wait on each.

Tradeoffs

  • Latency. ESCALATE adds human-loop time, by design. Don't put approvals on the hot path of a real-time agent.
  • Throughput. You can't approve 1000 escalations a minute. If your agent legitimately does that many operations, set a per-function override or an amount condition at /protections to auto-approve the safe subset, and keep the gate on the rest.
  • Unreviewed escalations. A review left pending never auto-executes. It expires after 24 hours; an expired review behaves like a rejection (the call never runs).

What's next