Enforcement

Asqav enforces agent behavior at three levels, each a different tradeoff between security guarantee and integration complexity. Most teams use all three for different tools, and marking a tool as hidden removes it from discovery entirely. Whichever level makes the call, Asqav signs the outcome server-side into a tamper-evident receipt, recorded by a party that is not the agent's operator and verifiable by anyone afterward.

Three-level model

Tier Guarantee Tool
Strong Agent cannot skip the check enforced_tool_call
Bounded Absence of gate signature is detectable gate_action
Detectable Tampering is cryptographically provable sign_action

High-risk mutations (database writes, file deletions, financial transactions) use the strong path. Routine operations use bounded. Everything gets the detectable layer regardless.

Strong enforcement

The enforced_tool_call MCP tool gates a tool call and signs the decision into the audit trail. Call it instead of the downstream tool.

mcp
# Request a tool call through the proxy
enforced_tool_call(
    tool_name="sql:execute",
    agent_id="agt_xxx",
    arguments='{"query": "DELETE FROM users WHERE inactive = true"}'
)
# Returns APPROVED, DENIED, or PENDING_APPROVAL

When a tool_endpoint is configured, the approved call is forwarded and the response captured, producing a bilateral receipt that signs request and response together.

Bounded enforcement

The gate_action MCP tool is a pre-execution gate. Call it before an irreversible action, then call complete_action to close the bilateral receipt.

mcp
# 1. Check before acting
gate_action(
    action_type="data:delete:users",
    agent_id="agt_xxx",
    tool_name="database",
    risk_context="Bulk deletion of inactive user records"
)
# Returns: {"decision": "APPROVED", "gate_id": "...", "signature_id": "..."}

# 2. Perform the action, then report the outcome
complete_action(
    gate_id="gate_xxx",
    result="Deleted 42 inactive records"
)
# Returns: bilateral receipt linking approval + outcome

Detectable enforcement

The sign_action tool records what happened with cryptographic proof. Every signature is hash-chained to the previous one using ML-DSA-65 (FIPS 204), so a tampered or omitted entry breaks the chain and fails verification.

python
import asqav

asqav.init(api_key="sk_...")
agent = asqav.Agent.create("my-agent")

# Every action is signed and hash-chained
sig1 = agent.sign("data:read", {"table": "users"})
sig2 = agent.sign("data:write", {"table": "users", "rows": 5})

# Verify the chain is intact
result = Asqav.verify(sig2["signature_id"])
# {"valid": true, "chain_intact": true}

Three-phase signing

For high-stakes actions, split signing into three phases: intent, decision, and execution. Each phase produces a signed receipt, and execution only proceeds if the policy evaluation approves it.

python
# Three-phase signing: intent, decision, execution
chain = agent.sign_with_phases("data:delete", {"table": "users"})
print(chain.approved)    # True if policy allowed it
print(chain.intent)      # signed intent receipt
print(chain.decision)    # policy evaluation result
print(chain.execution)   # signed execution receipt (only if approved)

If the policy denies the action, chain.execution is None and chain.approved is False.

Tool policies

Tool policies control how enforced_tool_call and gate_action handle specific tools. Set them via the ASQAV_PROXY_TOOLS environment variable or at runtime with create_tool_policy.

bash
export ASQAV_PROXY_TOOLS='{
  "sql:execute": {"risk_level": "high", "require_approval": true, "max_calls_per_minute": 5},
  "file:delete": {"blocked": true},
  "api:external": {"risk_level": "medium", "max_calls_per_minute": 30}
}'
Option Type Description
risk_level string "low", "medium", or "high". High-risk tools can require human approval.
require_approval boolean If true, high-risk tools trigger multi-party approval before execution.
max_calls_per_minute integer Rate limit. 0 means unlimited.
blocked boolean If true, the tool call is denied with a reason. The agent knows the tool exists but cannot use it.
hidden boolean If true, the tool is invisible to the agent. Stronger than blocked - the agent cannot discover or call the tool.
tool_endpoint string HTTP endpoint to forward approved calls to. Enables automatic bilateral receipts.

Bilateral receipts

A bilateral receipt signs both the request and the outcome together as one record. Create one via gate_action + complete_action (bounded), or via enforced_tool_call with a configured tool_endpoint (strong).

Observe mode

Observe mode logs what an enforcement check would do without calling the API or blocking any action. Use it to validate policies before deploying them.

python
import asqav
from asqav.extras.langchain import AsqavCallbackHandler

asqav.init(api_key="sk_...")

# observe=True logs what would be signed without signing or blocking
handler = AsqavCallbackHandler(agent_name="my-agent", observe=True)
# Log output: OBSERVE: would sign tool:search with context {...}

Semantic action patterns

The SDK ships named semantic patterns that resolve to glob equivalents, so a policy can target a category of actions without hand-writing the glob.

python
# Resolve a named pattern to its glob
import asqav
asqav.resolve_pattern("sql-destructive")  # "data:delete:*"
asqav.list_patterns()  # every built-in pattern name and its glob

# Use the resolved glob as the policy's action_pattern
# (POST /api/v1/policies, see the Policies page for the full schema)

The available pattern names are listed in the Policies reference.

Preflight explanations

A preflight check returns a plain-English explanation alongside the cleared/blocked outcome, so a denial is never a bare boolean.

python
result = agent.preflight("database:delete:users")
print(result.cleared)      # False
print(result.explanation)  # human-readable reason

Fail-closed semantics

If an enforcement check fails for any reason (network error, API timeout, malformed policy), the action is denied by default. The error is signed into the audit trail.

Claude Code hook

The SDK ships a Claude Code hook that acts as the gateway where policies are enforced. The asqav hook CLI reads each PreToolUse and PostToolUse event, checks the policy, and signs the decision into the audit trail. A blocked action exits non-zero so the tool call never runs.

Setup

Install the SDK and run the hook from your Claude Code settings:

bash
pip install asqav
export ASQAV_API_KEY="sk_..."
asqav hook pretool

Wire asqav hook pretool and asqav hook posttool into the matching hook events in your Claude Code settings.json. See the asqav-sdk repository for the hook reference.