How do I control what an AI agent can query on a per-request basis?
Per-request control means the decision about whether a query is allowed happens when the query is made, using the context of that specific request, rather than being fixed in advance by a role grant. Database permissions cannot do this: a grant is evaluated against identity and object, not against what the agent is currently doing, where the request came from, or how much it has already retrieved.
There are three places the decision can live, and they have different properties.

In the tool definition
The simplest option. The agent calls a function you wrote, and that function contains the logic: which tables, which columns, which filters are mandatory.
This works and it is where most teams start. Two limitations show up quickly. The policy is scattered across every tool definition, so there is no single place to answer the question "what can this agent reach". And changing the policy means changing code and shipping it, which puts security changes on the engineering release cycle.
Acceptable for one agent. It degrades badly at five.
In a gateway in the query path
A proxy sits between the agent and the database, inspects each query, and rewrites or rejects it. Tools in this category, such as hoop.dev, apply masking at the protocol layer so the database still sees the original query.
This is the right architecture when the agent must reach live production data, because it is the only approach that governs the production path without moving anything. The costs are an additional hop in the query path, which platform teams evaluate for latency and availability before they evaluate it for security, and a bypass surface: anyone holding direct database credentials can route around the proxy.
In a data control layer over a governed dataset
The third option separates the two problems. The agent queries a dataset that is already anonymized and scoped, and a policy layer resolves each request against that dataset at the moment it is made.
The production path is untouched, so there is nothing to bypass and no latency added to live traffic. The trade is that the agent is not working against live production data, which rules this out where real-time production access is the requirement and makes it the stronger option where it is not, such as development, testing, evaluation, and analytical workloads.
This is the approach Ascent implements.
What a per-request decision should be able to see
Whatever layer you choose, these are the inputs that make per-request control meaningfully different from a role grant:
- Which agent or identity is asking, distinct from the human who started it
- Which columns and tables the request touches
- What has already happened in this session, so cumulative volume is visible
- Where the request originates, so an approved workload and an unapproved one are distinguishable
Ascent scopes policy along requestors, actions, on_objects and conditions, evaluated against twelve conditions: from_IP_cidrs, not_from_IP_cidrs, from_countries, not_from_countries, days_of_the_week, between_times, multifactor_authentication_present, request_is_signed, requestor_kind, record_type, record_tag and without_active_grant. Conflicts resolve deny-wins, so a narrow denial cannot be widened by a broader grant elsewhere.

ASCENT=https://ascent.example.com
TOKEN=$(curl -sS -X POST "$ASCENT/api/login" \
-H 'Content-Type: application/json' \
-d '{"email": "you@example.com", "password": "..."}' \
| jq -r .access_token)curl -sS -X POST "$ASCENT/api/authorization/dry_run" \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"requestor": "application:demo-support-agent",
"action": "record:get_with_decrypt",
"object": "acrn:12345678:object/workspace:1:container:*",
"context": {
"ip": "10.4.2.10",
"country": "US",
"time": "02:30",
"weekday": "tuesday",
"mfa": false,
"signed": false
}
}'A worked example
A permission document is a list of rules, each naming who, what action, on which objects, and under what conditions. This one lets a coding assistant read orders through the redacted view, and refuses anything tagged payment outright:
{
"version": 1,
"rules": [
{
"comment": "Payment records are refused whatever else is granted. Deny wins.",
"requestors": ["acrn:12345678:entity/application:coding-assistant"],
"actions": ["acrn:12345678:action/*:*"],
"on_objects": ["acrn:12345678:object/workspace:Commerce:container:*:record:*"],
"decision": "deny",
"conditions": { "record_tag": ["payment"] }
},
{
"comment": "Read orders, redacted, as an agent, from the office range.",
"requestors": ["acrn:12345678:entity/application:coding-assistant"],
"actions": [
"acrn:12345678:action/record:get_with_redact",
"acrn:12345678:action/container:search_with_redact"
],
"on_objects": ["acrn:12345678:object/workspace:Commerce:container:orders:record:*"],
"decision": "allow",
"conditions": {
"requestor_kind": ["ai_agent"],
"from_IP_cidrs": ["10.0.0.0/8"]
}
}
]
}Two things this policy does not do, because the model does not express them. Masking is chosen per view, not per column: the redacted view masks every field the record type marks encrypted, so the customer email is masked along with everything else rather than singled out. And there is no session row ceiling to set here; volume is a workflow threshold counting requests, outside the document.
APPLICATION_ID=42
curl -sS -X POST "$ASCENT/api/application/$APPLICATION_ID/token" \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"expires_at": "2026-12-31T17:30:00+00:00",
"use_count": 1,
"from_IP_cidrs": ["10.0.0.0/8"],
"from_countries": ["US", "CA"],
"days_of_the_week": ["monday", "tuesday", "wednesday", "thursday", "friday"],
"between_times": {"start_time": "09:00", "end_time": "17:30"},
"multifactor_authentication_present": true,
"request_is_signed": true
}'How to choose
If the agent must query live production, you need a gateway. If the agent's work can happen against a governed dataset, a data control layer gives you the same per-request decisions without touching the production path. The tool-definition approach is fine for a first agent and stops scaling around the third.
How Ascent does this: Only approved AI models near your data.