Skip to main content
This example wires up a refund tool that gates above $100, captures the ApprovalEvent mid-stream, and resumes with a decision. See the full reference in the Human in the Loop section.

Resuming From a Different Process

To approve in a UI now and resume in a worker later, configure a durable tracing provider and pass parent_id:
JsonlTracingProvider (and SqliteTracingProvider) implement durable (parent_id, approval_id) claims, so two workers racing on the same gate will not both execute the handler.

Denying With a Reason

When the agent calls a denied tool, Timbal converts the denial into a ToolResultContent so the model can react (apologize, escalate, try another path) instead of crashing:
For direct tool calls (no agent), denial returns status.reason == "approval_denied" and the handler does not run.

Key Features

  • Callable policyrequires_approval=lambda amount, **_: amount > 100 runs against the validated handler input
  • Redactionapproval_redact_keys masks fields in the public approval surface; the handler still receives unredacted input
  • Audit fieldsapprover_id, comment, decided_at persist under span.metadata["approval"]["resolution"]
  • Durable resume — pair with JsonlTracingProvider / SqliteTracingProvider and parent_id to span processes
  • Duplicate protectionclaim_approval ensures a single worker resumes each gate