Start with a tool allowlist
For a file-reading agent, useallowedTools: ["read", "glob", "grep"]. For a text-only assistant, use disallowedTools: ["*"]. These are Agent constructor options; the built-in tools example includes a complete runnable file-reading agent.
A nonempty allowlist restricts tools and grants matching operations, subject to effective deny rules. A denylist match wins over an allowlist match. allowedTools: [] is unrestricted, and explicitly supplying either list replaces the facade’s default hidden-tool profile. An allowlist cannot create a tool that the selected model or runtime has not made available.
disallowedTools: ["*"] also blocks schema submission through StructuredOutput. For a schema-only task, use
allowedTools: ["StructuredOutput"].Rules apply to operations
Thepermission option is an ordered array of { permission, pattern, action }. Within a ruleset, the last matching rule wins. Put a broad rule first and narrower exceptions after it. action is "allow", "deny", or "ask".
The runtime also makes permission requests such as
external_directory when a tool accesses a location outside its workspace. These are permission names, not additional tools you can call.
Tool-list shorthand accepts scoped entries such as bash(npm:*) or task(reviewer). In the shell shorthand, : becomes a space, so bash(npm:*) matches the npm * command pattern. This is command-pattern authorization, not a guarantee about what an npm script will do. Use a controlled workspace and application-level boundaries for privileged execution.
Make a decision in your application
This complete example exposes a deterministic order lookup. The broad rule denies other operations; the later rule sends this tool’s requests through the approval path. The callback allows only the sample order. It deliberately does not addallowedTools: ["sdk_lookup_order"], because that matching grant could approve the request before the callback runs.
Use the TypeScript example setup.
PermissionDecision.allow(updatedInput) can replace input; validate the replacement as well. deny(message) supplies a reason. ask() delegates to the remaining user-decision path and may keep the request pending. A headless callback should normally return a concrete allow or deny decision.
Understand decision order
For a tool that is available, effective deny rules stop the operation before modes or callbacks can approve it. A matching allow can also settle the request immediately. Rules from the Agent/session and the runtime tool lists are evaluated together; an effective deny from either wins. Unresolved requests pass through plugin permission decisions, the selected permission mode, and then the callback/user-decision path. Consequently,canUseTool is not a universal tool interceptor. An allow rule, accepted edit, bypass mode, or confident automatic classification can settle a request first. Use a denylist or effective deny rule for unconditional restrictions, and validate sensitive operations inside their implementation.
Plugin permission hooks can explicitly keep a request on the approval path. The
autoPermissionClassifierModel option chooses the classifier; this can incur extra provider usage. Prefer deterministic rules when the decision must not depend on a model.
Keep approval lifetimes bounded
Callbacks run in your application process over the runtime control channel. The server stores a descriptor, not the function. Rebind callbacks after an application restart; an explicit resume override ofcanUseTool: false removes a prior registration.
A callback timeout or unavailable control channel can leave the user-decision path pending. Pass an application cancellation signal or deadline, as in the example, rather than assuming every failure becomes a denial. On an idle session, setPermissionMode(mode) changes its policy, and getAppliedSettings() shows the applied configuration.