Pre-execution verification for AI agents.
Use this guide when you need AI agent security, zero-trust approval flows, tool call verification, and runtime policy enforcement before an agent touches external systems.
Overview
QWED Agent Verification provides:
- Pre-execution checks before agents act
- Budget enforcement to limit costs
- Risk assessment for each action
- Activity logging for audit trails
Registering an Agent
from qwed_sdk import QWEDClient
client = QWEDClient(api_key="qwed_...")
agent = client.register_agent(
name="DataAnalyst",
type="supervised", # supervised, autonomous, trusted
principal_id="user_123",
permissions={
"allowed_engines": ["math", "logic", "sql"],
"blocked_tools": ["execute_code"],
},
budget={
"max_daily_cost_usd": 100,
"max_requests_per_hour": 500,
}
)
print(agent["agent_id"]) # agent_abc123
print(agent["agent_token"]) # qwed_agent_xyz...
Verifying actions
Before an agent executes an action, you must provide an ActionContext with a conversation_id and a monotonically increasing step_number. These fields are required — requests without them are rejected.
decision = client.verify_action(
agent_id="agent_abc123",
action={
"type": "execute_sql",
"query": "SELECT * FROM users"
},
context={
"conversation_id": "conv_xyz",
"step_number": 1,
"user_intent": "Get user list"
}
)
if decision["decision"] == "APPROVED":
execute_query(query)
elif decision["decision"] == "DENIED":
print("Action blocked:", decision["error"])
elif decision["decision"] == "PENDING":
request_human_approval()
elif decision["decision"] == "BUDGET_EXCEEDED":
print("Budget limit reached:", decision["error"])
Conversation controls
QWED enforces runtime guardrails that prevent agents from replaying actions, running in infinite loops, or exceeding conversation length limits. These checks run automatically on every verify_action call.
How it works
Each call to verify_action must include a conversation_id (identifying the current session) and a step_number (a positive integer that increases with each action in that session). QWED uses these fields to enforce three controls:
| Control | Limit | Error code |
|---|
| Conversation length | 50 steps per conversation | QWED-AGENT-LOOP-001 |
| Replay detection | Each step number can only be used once | QWED-AGENT-LOOP-002 |
| Repetitive loop detection | Max 2 consecutive identical actions | QWED-AGENT-LOOP-003 |
Incrementing steps correctly
The step_number must be strictly greater than any previously committed step within the same conversation. If an action is denied (for example, due to a loop), that step number is not consumed — you can retry the same step with a different action.
# Step 1: approved
client.verify_action(
agent_id="agent_abc123",
action={"type": "calculate", "query": "2+2"},
context={"conversation_id": "conv_1", "step_number": 1}
)
# Step 2: same action, still approved (first repeat)
client.verify_action(
agent_id="agent_abc123",
action={"type": "calculate", "query": "2+2"},
context={"conversation_id": "conv_1", "step_number": 2}
)
# Step 3: same action again — denied as repetitive loop
result = client.verify_action(
agent_id="agent_abc123",
action={"type": "calculate", "query": "2+2"},
context={"conversation_id": "conv_1", "step_number": 3}
)
# result["decision"] == "DENIED"
# result["error"]["code"] == "QWED-AGENT-LOOP-003"
# Step 3 retry: a different action succeeds on the same step number
client.verify_action(
agent_id="agent_abc123",
action={"type": "verify_logic", "query": "x > 1"},
context={"conversation_id": "conv_1", "step_number": 3}
)
If your agent framework retries failed actions automatically, make sure it does not reuse the same step_number for a previously approved step. Replayed step numbers are always rejected.
Trust Levels
| Level | Value | Description |
|---|
| UNTRUSTED | 0 | No autonomous actions |
| SUPERVISED | 1 | Low-risk autonomous |
| AUTONOMOUS | 2 | Most actions autonomous |
| TRUSTED | 3 | Full autonomy |
Risk Assessment
Actions are assessed for risk:
| Risk | Examples |
|---|
| LOW | read_file, database_read |
| MEDIUM | send_email, api_call |
| HIGH | file_write, database_write |
| CRITICAL | execute_code, file_delete, DROP |
Decision Matrix
| Trust Level | LOW Risk | MEDIUM Risk | HIGH Risk | CRITICAL Risk |
|---|
| 0 (Untrusted) | PENDING | DENIED | DENIED | DENIED |
| 1 (Supervised) | APPROVED | PENDING | DENIED | DENIED |
| 2 (Autonomous) | APPROVED | APPROVED | PENDING | DENIED |
| 3 (Trusted) | APPROVED | APPROVED | APPROVED | APPROVED |
Budget Enforcement
# Check remaining budget
budget = client.get_agent_budget("agent_abc123")
print(budget)
# {
# "cost": {"max_daily_usd": 100, "current_daily_usd": 45.50},
# "requests": {"max_per_hour": 500, "current_hour": 123}
# }
Activity logging
# Get agent activity
activity = client.get_agent_activity("agent_abc123", limit=10)
for entry in activity:
print(f"{entry['timestamp']}: {entry['action_type']} -> {entry['decision']}")
Runtime hardening
QWED enforces several runtime controls to prevent agent misuse, infinite loops, and replay attacks. These protections operate at the verification kernel level and cannot be bypassed by agents.
Action context enforcement
Every verify_action call requires an ActionContext with:
| Field | Type | Required | Description |
|---|
conversation_id | string | Yes | Unique identifier for the conversation/session |
step_number | integer | Yes | Monotonically increasing step counter (must be >= 1) |
user_intent | string | No | Human-readable description of the user’s goal |
The step number must increase with each action in a conversation. Attempts to reuse or decrement step numbers are rejected.
Replay and loop detection
The agent service detects and blocks three types of problematic patterns:
| Pattern | Error code | Description |
|---|
| Step replay | QWED-AGENT-LOOP-002 | Submitting an action with a step_number that was already used in the conversation |
| Repetitive loop | QWED-AGENT-LOOP-003 | Submitting the same action (identical fingerprint) more than 2 consecutive times |
| Step limit exceeded | QWED-AGENT-LOOP-001 | Exceeding the maximum of 50 steps per conversation |
Actions are fingerprinted deterministically using action_type, query, code, target, and parameters. If a loop is detected, the conversation state is not advanced — the agent can recover by submitting a different action at the same step number.
# Step 1: approved
client.verify_action(agent_id, action={"type": "calculate", "query": "2+2"},
context={"conversation_id": "conv_1", "step_number": 1})
# Step 1 again: DENIED (replay)
client.verify_action(agent_id, action={"type": "calculate", "query": "2+2"},
context={"conversation_id": "conv_1", "step_number": 1})
# -> {"decision": "DENIED", "error": {"code": "QWED-AGENT-LOOP-002"}}
In-flight reservation system
When a verify_action call is being processed, QWED reserves the step number to prevent concurrent requests from claiming the same step. The reservation is released if the action is denied, allowing the agent to retry with a different action at the same step.
Budget denial behavior
When a budget check fails, the conversation step is not consumed. This means the agent can retry the same step number after the budget resets without triggering a replay detection error.
Fail-closed rate limiting
The Redis-backed sliding window rate limiter fails closed when Redis is unavailable. If the Redis backend encounters an error, all requests are denied rather than allowed, preventing uncontrolled access during infrastructure failures. When Redis is entirely absent at startup, a local in-memory fallback limiter is used instead.
Environment integrity verification
On API server startup, QWED runs an environment integrity check (via StartupHookGuard) before initializing the database. If the environment is compromised, the server refuses to start. This prevents operation in tampered runtime environments.
Timing-safe token verification
Agent token verification uses hmac.compare_digest for constant-time comparison, preventing timing side-channel attacks against agent authentication.
Framework integration
LangChain
from qwed_sdk.langchain import QWEDVerificationCallback
agent = initialize_agent(
tools=[...],
callbacks=[QWEDVerificationCallback(agent_id="agent_abc123")]
)
CrewAI
from qwed_sdk.crewai import QWEDVerifiedAgent
analyst = QWEDVerifiedAgent(
role="Analyst",
goal="Analyze data",
agent_id="agent_abc123"
)