CertSafari
    Anthropic Claude Certified Architect – Professional (CCAR-P)· Lessons

    Domain 5 · Lesson 28/38

    Programmatic Approval Flows: Agent SDK and Managed Agents

    Apply human-in-the-loop validation strategies

    12 min read
    2.8% of exam
    5 sources
    Published 4 Oct 2026
    Docs as of 30 Sep 2026

    What you will be able to do

    • Implement a canUseTool callback that sends tool calls to a human and returns allow or deny
    • Choose among approve, modify, remember, reject and redirect responses at a checkpoint
    • Configure Managed Agents permission policies so risky tools wait for confirmation
    • Walk through the always_ask confirmation event flow and explain what a client can and cannot override

    1.canUseTool: sending tool calls to a person

    When you build your own agent with the Agent SDK, there is no built-in prompt UI, so you supply the human checkpoint yourself. The SDK calls your canUseTool callback (can_use_tool in Python) when Claude wants a tool that isn't "auto-approved by a permission rule or permission mode." The same callback receives clarifying questions: to handle those separately, "Check if tool_name == \"AskUserQuestion\" to handle it differently." Your callback shows the request to a person and returns their decision. With an allow, the tool runs. With a deny, the tool doesn't run and Claude sees your message.

    Checkpoint 1 of 6· Fill the gap

    Which parameter name lets the callback pass the (possibly edited) input back when the human approves?

    async def can_use_tool(tool_name, input_data, context):
        print(f"Claude wants to use {tool_name}")
        approved = await ask_user("Allow this action?")
    
        if approved:
            return PermissionResultAllow( ? =input_data)
        return PermissionResultDeny(message="User declined")

    A plain yes or no wastes much of what the reviewer knows. The SDK documentation lists six ways to respond. Several of them let the human correct the agent instead of just stopping it. Approving with changes means returning an allow whose updated_input has been edited, for example to sanitize a path. Approving and remembering turns one decision into a permission rule, which cuts down on repeat prompts.

    The six response patterns at an SDK approval checkpoint
    ResponseWhat the human does
    ApproveLet the tool execute as Claude requested
    Approve with changesModify the input before execution (sanitize paths, add constraints)
    Approve and rememberEcho a suggested permission rule back so matching calls skip the prompt next time
    RejectBlock the tool and tell Claude why
    Suggest alternativeBlock but guide Claude toward what the user wants instead
    Redirect entirelyUse streaming input to send Claude a completely new instruction

    The last two patterns differ from a plain reject in what they aim for. A reject says no and gives the reason. Suggest alternative is also a block, but the message steers Claude toward what the user wants instead, and Claude sees that message when the tool is denied. Redirect entirely goes further: it doesn't answer the pending request but uses streaming input to send Claude a new instruction. That is why the SDK's approval example feeds its prompt through a streaming generator. The documentation names these two patterns without a dedicated code example.

    The callback only fires for calls that no rule or mode has already settled. The permission docs say that "Ask rules prompt for confirmation whenever Claude Code tries to use the specified tool," so an ask rule keeps a tool in front of a human. A mode can also remove the prompt: dontAsk "Auto-denies every call that would otherwise prompt," which suits locked-down CI where nobody is there to answer. Plan mode is for exploring, and the docs say Claude Code "blocks edits until you approve a plan." That approval is of the plan, not of each individual edit.

    Checkpoint 2 of 6· Exam question

    A DevOps team runs a Claude agent unattended inside a CI pipeline to triage failing builds. No human is available to respond to interactive approval prompts during the run, so any tool call that isn't already pre-approved must be denied outright rather than left waiting on a prompt. Which permission mode configuration achieves this?

    Sources123

    2.Hooks as a second checkpoint layer

    Hooks let you step in at fixed points in execution. The SDK hooks documentation lists human checkpoints as one use: "Require human approval for sensitive actions like database writes or API calls." When an event such as PreToolUse fires, the SDK collects the registered hooks, and matchers decide which ones run. The callbacks then execute and return a decision. A hook can deny a call in code before anyone sees it, for example to protect .env files, so the human only reviews calls that pass the automated rules. This keeps reviewers focused on the cases that need judgement.

    Checkpoint 3 of 6· Check yourself

    Which of these hook uses does the SDK documentation list as the way to put a person in the loop?

    Hooks and canUseTool can work together. In the SDK's Python approval example, a PreToolUse hook is registered next to can_use_tool, and the source comments it as a "Required workaround: dummy hook keeps the stream open for can_use_tool." The hook there makes no decision. It only keeps the stream open so the callback can reach the human.

    Sources41

    3.Managed Agents: always_allow, always_ask and auto

    In Managed Agents, the server runs the tools, and permission policies control whether a call runs, waits for you or gets evaluated. always_ask is the explicit human checkpoint: the session pauses until you approve. The defaults differ by toolset: "the agent toolset defaults to always_allow, and MCP toolsets default to always_ask." The MCP default exists so "new tools added to an MCP server do not execute in your application without approval." Custom tools fall outside this system, because your application runs them and you control them. You can override the default for a single tool, which is how you put one risky tool behind a checkpoint:

    Allow the agent toolset by default, but require confirmation before any bash commandyaml
    ---
    name: Coding Assistant
    model: claude-opus-5-5
    tools:
      - type: agent_toolset_20260401
        default_config:
          permission_policy:
            type: always_allow
        configs:
          - name: bash
            permission_policy:
              type: always_ask
    ---

    Under auto, the server evaluates every call and does one of three things: it runs the call, denies it as high-risk, or, when it "reaches no determination, the session pauses as it does under always_ask." So the human sees only the uncertain cases. There is one catch to know. The server treats user.message content as your intent, and that intent can get a call allowed. If you relay untrusted end-user text through user.message, that text counts as your intent too. The fix the documentation gives is a human checkpoint: "Configure always_ask on the tools you would not let that end user run without review." Changing a policy only affects sessions created after the change, because running sessions keep the configuration they started with.

    Checkpoint 4 of 6· Match them up

    Match each permission policy to how it involves a human

    Tap a term, then the definition that fits it.

    Sources5

    4.The confirmation round-trip

    When a call evaluates to ask, the session emits the tool-use event and goes idle with stop_reason.type set to requires_action. The blocking event IDs are listed in stop_reason.event_ids, and the session "waits indefinitely for a response." Your client answers each blocking event with a user.tool_confirmation. Once every one is resolved, the session goes back to running. Denials should explain themselves, because the agent receives your deny_message in its tool result and can choose a better path:

    Denying a paused call and pointing the agent at a safer targetpython
    # Or deny it with an explanation
    client.beta.sessions.events.send(
        session.id,
        events=[
            {
                "type": "user.tool_confirmation",
                "tool_use_id": mcp_tool_use_event.id,
                "result": "deny",
                "deny_message": "Don't create issues in the production project. Use the staging project.",
            },
        ],
    )

    Each tool-use event also carries evaluated_permission (allow, ask or deny), so you have a record of which calls reached a human. A reason_code is for your client to branch on and "keep in audit records, not text to display to end users."

    Checkpoint 5 of 6· Put it in order

    Put the steps of an always_ask confirmation in order

    1. 1.The session returns to running; allowed tools execute and denied tools return your deny_message
    2. 2.The session emits an agent.tool_use or agent.mcp_tool_use event
    3. 3.Your client sends a user.tool_confirmation with the event's ID as tool_use_id and result allow or deny
    4. 4.The session goes idle with stop_reason.type requires_action

    Checkpoint 6 of 6· Exam question

    An engineering lead wants to use a Claude agent to investigate a suspected security vulnerability across a large codebase, but insists that no file be modified until she has personally reviewed and approved the exact diff Claude intends to make. Which configuration best matches this workflow?

    Sources5

    Exam traps

    Each one states something that sounds right. Open it to see what is actually true.

    1. 1.Under the auto policy, a human operator can rescue a server denial by sending a user.tool_confirmation with result allow.Why is that wrong?

      High-risk denials under auto are final. A confirmation for any event whose evaluated_permission isn't ask gets a 400 error.

      Covered in Managed Agents: always_allow, always_ask and auto

    2. 2.Switching a tool to always_ask on the agent immediately puts a checkpoint on sessions that are already running.Why is that wrong?

      Policy updates only apply to sessions created after the update.

      Covered in Managed Agents: always_allow, always_ask and auto

    3. 3.Setting always_ask on the agent's tools also gates the custom tools your application defines.Why is that wrong?

      Permission policies don't cover custom tools. Your application runs them, so you have to add any human checkpoint yourself.

      Covered in Managed Agents: always_allow, always_ask and auto

    Practise it for real

    Put bash behind a human checkpoint in a Managed Agent and practise denying a call with guidance

    1. 1.Create an agent whose agent toolset defaults to always_allow, with a configs entry setting bash to always_ask

      Why: This puts a human checkpoint on the riskiest tool and leaves the others running freely

      You should see: The agent is created with bash set to always_ask

    2. 2.Start a new session and ask the agent to do something that needs a shell command

      Why: Only sessions created after the configuration change pick it up

      You should see: An agent.tool_use event for bash, followed by session.status_idle with stop_reason.type requires_action

    3. 3.Send a user.tool_confirmation with the event's ID as tool_use_id, result deny, and a deny_message describing a safer approach

      Why: A deny with a reason redirects the agent instead of just stopping it

      You should see: The session returns to running, and the agent receives a tool result saying the call was rejected, including your deny_message

    Stuck? Get a nudge

    Look up the blocking event IDs in stop_reason.event_ids on the idle event.

    Sources

    Every claim above is drawn from one of these pages, quoted as it was written on the date shown.

    1. 1.
      “auto-approved by a permission rule or permission mode”
      ↩︎ canUseTool: sending tool calls to a person
      “Check if tool_name == "AskUserQuestion" to handle it differently.”
      ↩︎ canUseTool: sending tool calls to a person
      “Approve with changes: modify the input before execution (for example, sanitize paths, add constraints)”
      ↩︎ canUseTool: sending tool calls to a person
      “Suggest alternative: block but guide Claude toward what the user wants instead”
      ↩︎ canUseTool: sending tool calls to a person
      “Required workaround: dummy hook keeps the stream open for can_use_tool”
      ↩︎ Hooks as a second checkpoint layer
    2. 2.
      “Ask rules prompt for confirmation whenever Claude Code tries to use the specified tool.”
      ↩︎ canUseTool: sending tool calls to a person
      “Auto-denies every call that would otherwise prompt”
      ↩︎ canUseTool: sending tool calls to a person
    3. 3.
      “Claude Code blocks edits until you approve a plan”
      ↩︎ canUseTool: sending tool calls to a person
    4. 4.
      “Require human approval for sensitive actions like database writes or API calls”
      ↩︎ Hooks as a second checkpoint layer
      “Matchers filter which hooks run”
      ↩︎ Hooks as a second checkpoint layer
    5. 5.
      “Each toolset kind has its own default: the agent toolset defaults to always_allow, and MCP toolsets default to always_ask.”
      ↩︎ Managed Agents: always_allow, always_ask and auto
      “This ensures that new tools added to an MCP server do not execute in your application without approval.”
      ↩︎ Managed Agents: always_allow, always_ask and auto
      “Configure always_ask on the tools you would not let that end user run without review.”
      ↩︎ Managed Agents: always_allow, always_ask and auto
      “If you relay untrusted end-user input in user.message events, the server reads that input as your intent too”
      ↩︎ Managed Agents: always_allow, always_ask and auto
      “The session waits indefinitely for a response.”
      ↩︎ The confirmation round-trip
      “Denied tools do not run, and the agent receives a tool result saying the call was rejected, including your deny_message.”
      ↩︎ The confirmation round-trip
      “A reason_code is a value for your client to branch on and keep in audit records, not text to display to end users.”
      ↩︎ The confirmation round-trip
      “The session keeps running, and your client cannot override the denial.”
      ↩︎ Exam trap 1
      “Running sessions keep the toolset configuration they were created with.”
      ↩︎ Exam trap 2
      “Custom tools are executed by your application and controlled by you, so they are not governed by permission policies.”
      ↩︎ Exam trap 3
      “The session keeps running, and your client cannot override the denial.”
      ↩︎ Prediction
      “When the server reaches no determination, the session pauses as it does under always_ask.”
      ↩︎ Checkpoint
      “Send a user.tool_confirmation event for each blocking event, passing the event ID in the tool_use_id parameter.”
      ↩︎ Checkpoint

    Ready to test yourself?

    Practise the 12 questions on this subdomain.

    Spotted a mistake, or was something unclear? Tell us.