Human-in-the-loop
Human-in-the-loop means a run can pause before a side effect. The pause is a runtime event with arguments, policy, and a decision — not a hidden callback in your route handler.
Why approvals are runtime events#
You need to answer:
- Which tool was about to run?
- With which arguments?
- Who approved or rejected?
- How did the run continue?
Those questions require structure. Studio’s approval center and traces consume the same events the runner emits.
Marking a tool#
import { tool } from "@shiro-sdk/core";
import { z } from "zod";
const deleteResource = tool({
name: "deleteResource",
description: "Permanently delete a cloud resource.",
requiresApproval: true,
approvalDescription: "Destructive delete — irreversible.",
parameters: z.object({
resourceId: z.string(),
}),
execute: async ({ resourceId }) => resources.delete(resourceId),
});Until approval is granted, execute should not run. Rejection is a valid outcome, not
necessarily an unexpected exception.
When to require approval#
| Require approval | Usually skip |
|---|---|
| Refunds, payouts | Read-only lookups |
| Deletes, revokes | Idempotent cache warmups |
| Deployments, permission changes | Formatting / pure compute |
| Outbound customer messages | Internal dry-run tools |
Guardrails vs approvals#
- Guardrails — policy checks that can allow, block, or modify a stage
- Approvals — explicit human (or external) decision gates on tool calls
Both belong in the runtime so they appear in traces. See core Guardrail types and
ApprovalManager / policies when you need custom reviewers.
Workflow#
model requests tool
→ approval.requested (Studio / reviewer notified)
→ approval.granted | approval.rejected
→ tool executes or run continues without the side effectBest practices#
- Approve the tool call, not the final chat sentence
- Include enough args for a reviewer to decide without reading code
- Log / trace the decision with the run id
- Train operators on Studio’s approval center during development
Common mistakes#
- Asking the model to “confirm” in natural language and treating that as security
- Retrying a rejected delete automatically
- Implementing approvals outside Shiro so Studio never sees the pause
- Approving after the mutation already happened