Governing tool calls
The four decisions, approval polling, run correlation and fail-modes.
Governing tool calls
When your agent asks the gateway about a tool call, it gets back one of four decisions. This page explains each one, how to group calls into a run and how failures are handled.
The four decisions
| Decision | What it means and what to do |
|---|---|
| allow | The call passed all checks. Run the tool. |
| deny | A guardrail, or the agent key's tool lists when they apply to native calls, blocked the call. Do not run the tool. The response includes a reason. |
| approval_required | A human must approve first. The response includes an approval id and a poll endpoint. Wait for approval, then retry. |
| rate_limited | The agent key went over its per-minute limit. Back off and retry later. |
Agent key tool lists
An agent key's allowed and blocked tool lists always apply to MCP server tools called through the proxy. They apply to native tool calls (the hook script and the Python SDK) only when the key has "Also apply tool lists to native tool calls" turned on. It is off by default, because lists written for MCP tools would otherwise deny Bash, Edit and other built-in tools of a key shared with Claude Code or Cursor.
When the setting is on, the native hook checks the lists first, before any guardrail or approval rule. A tool that is not on the allowed list, or is on the blocked list, is denied straight away. A key with no lists is not affected.
{
"decision": "deny",
"reason": "Tool 'Bash' is not allowed for this agent key"
}Handling approval
When the decision is approval_required, the gateway has created a request for a human to approve or deny. Poll the status endpoint until you get a final answer.
{
"decision": "approval_required",
"approval_id": 42,
"poll_endpoint": "/v1/mcp/approvals/42/status",
"expires_at": "2026-04-20T15:30:00+00:00"
}- Call GET /v1/mcp/approvals/{approval_id}/status with your agent key.
- When the status is approved, run the tool.
- When the status is denied, do not run it.
- When the status is expired, the window passed with no decision; treat it as a block.
On the MCP proxy path the same information comes back as a JSON-RPC error with code -32001, carrying the approval id, poll endpoint and expiry in error.data.
Monitor only mode
An Admin can set an agent key to monitor only, in the Mode setting of the Agent keys page. Use it to see what your rules would do to an agent before you enforce them. Every rule is still checked, but the call is never blocked or paused: the decision is always allow and the agent runs the tool.
- What is checked: Guardrails (a block or mask match), require approval rules, the key's allowed and blocked tool lists on the MCP proxy (and on native tool calls when the key's "Also apply tool lists to native tool calls" setting is on), and on the MCP proxy tools marked as requiring approval.
- No approvals: No approval request is created and nobody is notified, so there is nothing to poll.
- Still enforced: The key's rate limit, and on the MCP proxy the circuit breaker. They protect the gateway and your MCP servers rather than express policy, so a monitor only key can still get rate_limited.
- Tool list on the proxy: tools/list is not filtered by the key's tool lists, so the agent sees every tool.
When something matched, the allow response carries a monitored object saying what would have happened. On the MCP proxy the same object is in the tool result under _meta["verifywise.ai/monitored"]. See the API reference for its fields.
{
"decision": "allow",
"monitored": {
"would": "deny",
"reason": "PII detected",
"source": "guardrail",
"rule": { "id": 11, "name": "No emails" },
"outcomes": [
{ "would": "deny", "reason": "PII detected", "source": "guardrail", "rule": { "id": 11, "name": "No emails" } }
]
}
}In Activity these calls appear with the status Would block or Would need approval instead of blocked or approval required. When a call matched several things, the status is the most severe one and each match is listed in the call's events. The result summary is the tool's own result, as for any allowed call.
Grouping calls into a run
A run groups one turn of work: the model calls and the tool calls that belong together. They are tied by a shared run id.
- Tool calls: The native hook reuses the agent session id it already sends, so tool calls are grouped automatically.
- Model calls: Send the same id on your model requests using the header x-vw-agent-run-id. The aliases x-session-id and helicone-session-id are also accepted.
Failures and fail-modes
- Gateway unreachable: With VW_FAIL_MODE=open (the default) the call runs anyway, so an outage never blocks your agent. Set it to closed to block instead.
- Approval timed out: Controlled by VW_APPROVAL_FAIL_MODE (default closed, which blocks). Set it to open to allow on timeout.
- Rate limited: Follows VW_FAIL_MODE. The call is an infra outcome, not a policy block.